Rework 'compact' mode for traces

- Trace-if-false are now completely discarded in compact mode.

  - Only the label (i.e. first trace argument) is preserved.

  - When compiling with tracing _compact_, the first label MUST unify to
    a string. This shouldn't be an issue generally speaking and would
    enforce that traces follow the pattern

    ```
    label: arg_0[, arg_1, ..., arg_n]
    ```

  Note that what isn't obvious with these changes is that we now support
  what the "emit" keyword was trying to achieve; as we compile now with
  user-defined traces only, and in compact mode to only keep event
  labels in the final contract; while allowing larger payloads with
  verbose tracing.
This commit is contained in:
KtorZ 2024-07-19 10:51:47 +02:00
parent a9d782e206
commit d6fd37c80e
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
4 changed files with 92 additions and 100 deletions

View File

@ -1957,6 +1957,10 @@ pub enum TraceLevel {
}
impl Tracing {
pub fn verbose() -> Self {
Tracing::All(TraceLevel::Verbose)
}
pub fn silent() -> Self {
Tracing::All(TraceLevel::Silent)
}

View File

@ -18,6 +18,7 @@ fn check_module(
ast: UntypedModule,
extra: Vec<(String, UntypedModule)>,
kind: ModuleKind,
tracing: Tracing,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
let id_gen = IdGenerator::new();
@ -47,7 +48,7 @@ fn check_module(
kind,
"test/project",
&module_types,
Tracing::All(TraceLevel::Verbose),
tracing,
&mut warnings,
);
@ -57,20 +58,27 @@ fn check_module(
}
fn check(ast: UntypedModule) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, Vec::new(), ModuleKind::Lib)
check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::verbose())
}
fn check_with_verbosity(
ast: UntypedModule,
level: TraceLevel,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::All(level))
}
fn check_with_deps(
ast: UntypedModule,
extra: Vec<(String, UntypedModule)>,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, extra, ModuleKind::Lib)
check_module(ast, extra, ModuleKind::Lib, Tracing::verbose())
}
fn check_validator(
ast: UntypedModule,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, Vec::new(), ModuleKind::Validator)
check_module(ast, Vec::new(), ModuleKind::Validator, Tracing::verbose())
}
#[test]
@ -1288,8 +1296,32 @@ fn trace_non_strings() {
True
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn trace_string_label_compact() {
let source_code = r#"
test foo() {
trace @"foo": [1,2,3]
True
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn trace_non_string_label_compact() {
let source_code = r#"
test foo() {
trace(14 + 42)
True
}
"#;
assert!(matches!(
check(parse(source_code)),
check_with_verbosity(parse(source_code), TraceLevel::Compact),
Err((_, Error::CouldNotUnify { .. }))
))
}

View File

@ -24,7 +24,6 @@ use crate::{
},
expr::{FnStyle, TypedExpr, UntypedExpr},
format,
line_numbers::LineNumbers,
tipo::{fields::FieldMap, DefaultFunction, PatternConstructor, TypeVar},
IdGenerator,
};
@ -41,7 +40,6 @@ pub(crate) fn infer_function(
module_name: &str,
hydrators: &mut HashMap<String, Hydrator>,
environment: &mut Environment<'_>,
lines: &LineNumbers,
tracing: Tracing,
) -> Result<Function<Rc<Type>, TypedExpr, TypedArg>, Error> {
if let Some(typed_fun) = environment.inferred_functions.get(&fun.name) {
@ -122,7 +120,7 @@ pub(crate) fn infer_function(
.remove(name)
.unwrap_or_else(|| panic!("Could not find hydrator for fn {name}"));
let mut expr_typer = ExprTyper::new(environment, lines, tracing);
let mut expr_typer = ExprTyper::new(environment, tracing);
expr_typer.hydrator = hydrator;
expr_typer.not_yet_inferred = BTreeSet::from_iter(hydrators.keys().cloned());
@ -155,12 +153,11 @@ pub(crate) fn infer_function(
environment.current_module,
hydrators,
environment,
lines,
tracing,
)?;
// Then, try again the entire function definition.
return infer_function(fun, module_name, hydrators, environment, lines, tracing);
return infer_function(fun, module_name, hydrators, environment, tracing);
}
let (arguments, body, return_type) = inferred?;
@ -223,8 +220,6 @@ pub(crate) fn infer_function(
#[derive(Debug)]
pub(crate) struct ExprTyper<'a, 'b> {
pub(crate) lines: &'a LineNumbers,
pub(crate) environment: &'a mut Environment<'b>,
// We tweak the tracing behavior during type-check. Traces are either kept or left out of the
@ -244,18 +239,13 @@ pub(crate) struct ExprTyper<'a, 'b> {
}
impl<'a, 'b> ExprTyper<'a, 'b> {
pub fn new(
environment: &'a mut Environment<'b>,
lines: &'a LineNumbers,
tracing: Tracing,
) -> Self {
pub fn new(environment: &'a mut Environment<'b>, tracing: Tracing) -> Self {
Self {
hydrator: Hydrator::new(),
not_yet_inferred: BTreeSet::new(),
environment,
tracing,
ungeneralised_function_used: false,
lines,
}
}
@ -681,25 +671,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.to_pretty_string(999)
),
}),
TraceLevel::Compact => Some(TypedExpr::String {
location,
tipo: string(),
value: self
.lines
.line_and_column_number(location.start)
.expect("Spans are within bounds.")
.to_string(),
}),
TraceLevel::Silent => None,
TraceLevel::Compact | TraceLevel::Silent => None,
};
let typed_value = self.infer(value)?;
self.unify(bool(), typed_value.tipo(), typed_value.location(), false)?;
match self.tracing.trace_level(false) {
TraceLevel::Silent => Ok(typed_value),
TraceLevel::Verbose | TraceLevel::Compact => Ok(TypedExpr::If {
match text {
None => Ok(typed_value),
Some(text) => Ok(TypedExpr::If {
location,
branches: vec1::vec1![IfBranch {
condition: typed_value,
@ -710,7 +691,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
final_else: Box::new(TypedExpr::Trace {
location,
tipo: bool(),
text: Box::new(text.expect("TraceLevel::Silent excluded from pattern-guard")),
text: Box::new(text),
then: Box::new(var_false),
}),
tipo: bool(),
@ -2426,29 +2407,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
label: UntypedExpr,
arguments: Vec<UntypedExpr>,
) -> Result<TypedExpr, Error> {
let label = self.infer_trace_arg(label)?;
let typed_arguments = arguments
.into_iter()
.map(|arg| self.infer_trace_arg(arg))
.collect::<Result<Vec<_>, Error>>()?;
let text = if typed_arguments.is_empty() {
label
} else {
let delimiter = |ix| TypedExpr::String {
location: Span::empty(),
tipo: string(),
value: if ix == 0 { ": " } else { ", " }.to_string(),
};
typed_arguments
.into_iter()
.enumerate()
.fold(label, |text, (ix, arg)| {
append_string_expr(append_string_expr(text, delimiter(ix)), arg)
})
};
let then = self.infer(then)?;
let tipo = then.tipo();
@ -2461,26 +2424,42 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
match self.tracing.trace_level(false) {
TraceLevel::Silent => Ok(then),
TraceLevel::Compact => Ok(TypedExpr::Trace {
location,
tipo,
then: Box::new(then),
text: Box::new(TypedExpr::String {
TraceLevel::Compact => {
let text = self.infer(label)?;
self.unify(string(), text.tipo(), text.location(), false)?;
Ok(TypedExpr::Trace {
location,
tipo: string(),
value: self
.lines
.line_and_column_number(location.start)
.expect("Spans are within bounds.")
.to_string(),
}),
}),
TraceLevel::Verbose => Ok(TypedExpr::Trace {
location,
tipo,
then: Box::new(then),
text: Box::new(text),
}),
tipo,
then: Box::new(then),
text: Box::new(text),
})
}
TraceLevel::Verbose => {
let label = self.infer_trace_arg(label)?;
let text = if typed_arguments.is_empty() {
label
} else {
let delimiter = |ix| TypedExpr::String {
location: Span::empty(),
tipo: string(),
value: if ix == 0 { ": " } else { ", " }.to_string(),
};
typed_arguments
.into_iter()
.enumerate()
.fold(label, |text, (ix, arg)| {
append_string_expr(append_string_expr(text, delimiter(ix)), arg)
})
};
Ok(TypedExpr::Trace {
location,
tipo,
then: Box::new(then),
text: Box::new(text),
})
}
}
}

View File

@ -13,7 +13,6 @@ use crate::{
},
builtins,
builtins::{fuzzer, generic_var},
line_numbers::LineNumbers,
tipo::{expr::infer_function, Span, Type, TypeVar},
IdGenerator,
};
@ -86,14 +85,8 @@ impl UntypedModule {
}
for def in consts.into_iter().chain(not_consts) {
let definition = infer_definition(
def,
&module_name,
&mut hydrators,
&mut environment,
&self.lines,
tracing,
)?;
let definition =
infer_definition(def, &module_name, &mut hydrators, &mut environment, tracing)?;
definitions.push(definition);
}
@ -162,7 +155,6 @@ fn infer_definition(
module_name: &String,
hydrators: &mut HashMap<String, Hydrator>,
environment: &mut Environment<'_>,
lines: &LineNumbers,
tracing: Tracing,
) -> Result<TypedDefinition, Error> {
match def {
@ -171,7 +163,6 @@ fn infer_definition(
module_name,
hydrators,
environment,
lines,
tracing,
)?)),
@ -228,7 +219,7 @@ fn infer_definition(
}
let mut typed_fun =
infer_function(&fun, module_name, hydrators, environment, lines, tracing)?;
infer_function(&fun, module_name, hydrators, environment, tracing)?;
if !typed_fun.return_type.is_bool() {
return Err(Error::ValidatorMustReturnBool {
@ -267,14 +258,8 @@ fn infer_definition(
let params = params.into_iter().chain(other.arguments);
other.arguments = params.collect();
let mut other_typed_fun = infer_function(
&other,
module_name,
hydrators,
environment,
lines,
tracing,
)?;
let mut other_typed_fun =
infer_function(&other, module_name, hydrators, environment, tracing)?;
if !other_typed_fun.return_type.is_bool() {
return Err(Error::ValidatorMustReturnBool {
@ -338,8 +323,7 @@ fn infer_definition(
});
}
let typed_via =
ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?;
let typed_via = ExprTyper::new(environment, tracing).infer(arg.via.clone())?;
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
@ -404,14 +388,7 @@ fn infer_definition(
None => Ok((None, None)),
}?;
let typed_f = infer_function(
&f.into(),
module_name,
hydrators,
environment,
lines,
tracing,
)?;
let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?;
environment.unify(
typed_f.return_type.clone(),
@ -629,7 +606,7 @@ fn infer_definition(
tipo: _,
}) => {
let typed_expr =
ExprTyper::new(environment, lines, tracing).infer_const(&annotation, *value)?;
ExprTyper::new(environment, tracing).infer_const(&annotation, *value)?;
let tipo = typed_expr.tipo();