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 { impl Tracing {
pub fn verbose() -> Self {
Tracing::All(TraceLevel::Verbose)
}
pub fn silent() -> Self { pub fn silent() -> Self {
Tracing::All(TraceLevel::Silent) Tracing::All(TraceLevel::Silent)
} }

View File

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

View File

@ -24,7 +24,6 @@ use crate::{
}, },
expr::{FnStyle, TypedExpr, UntypedExpr}, expr::{FnStyle, TypedExpr, UntypedExpr},
format, format,
line_numbers::LineNumbers,
tipo::{fields::FieldMap, DefaultFunction, PatternConstructor, TypeVar}, tipo::{fields::FieldMap, DefaultFunction, PatternConstructor, TypeVar},
IdGenerator, IdGenerator,
}; };
@ -41,7 +40,6 @@ pub(crate) fn infer_function(
module_name: &str, module_name: &str,
hydrators: &mut HashMap<String, Hydrator>, hydrators: &mut HashMap<String, Hydrator>,
environment: &mut Environment<'_>, environment: &mut Environment<'_>,
lines: &LineNumbers,
tracing: Tracing, tracing: Tracing,
) -> Result<Function<Rc<Type>, TypedExpr, TypedArg>, Error> { ) -> Result<Function<Rc<Type>, TypedExpr, TypedArg>, Error> {
if let Some(typed_fun) = environment.inferred_functions.get(&fun.name) { if let Some(typed_fun) = environment.inferred_functions.get(&fun.name) {
@ -122,7 +120,7 @@ pub(crate) fn infer_function(
.remove(name) .remove(name)
.unwrap_or_else(|| panic!("Could not find hydrator for fn {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.hydrator = hydrator;
expr_typer.not_yet_inferred = BTreeSet::from_iter(hydrators.keys().cloned()); expr_typer.not_yet_inferred = BTreeSet::from_iter(hydrators.keys().cloned());
@ -155,12 +153,11 @@ pub(crate) fn infer_function(
environment.current_module, environment.current_module,
hydrators, hydrators,
environment, environment,
lines,
tracing, tracing,
)?; )?;
// Then, try again the entire function definition. // 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?; let (arguments, body, return_type) = inferred?;
@ -223,8 +220,6 @@ pub(crate) fn infer_function(
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ExprTyper<'a, 'b> { pub(crate) struct ExprTyper<'a, 'b> {
pub(crate) lines: &'a LineNumbers,
pub(crate) environment: &'a mut Environment<'b>, pub(crate) environment: &'a mut Environment<'b>,
// We tweak the tracing behavior during type-check. Traces are either kept or left out of the // 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> { impl<'a, 'b> ExprTyper<'a, 'b> {
pub fn new( pub fn new(environment: &'a mut Environment<'b>, tracing: Tracing) -> Self {
environment: &'a mut Environment<'b>,
lines: &'a LineNumbers,
tracing: Tracing,
) -> Self {
Self { Self {
hydrator: Hydrator::new(), hydrator: Hydrator::new(),
not_yet_inferred: BTreeSet::new(), not_yet_inferred: BTreeSet::new(),
environment, environment,
tracing, tracing,
ungeneralised_function_used: false, ungeneralised_function_used: false,
lines,
} }
} }
@ -681,25 +671,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.to_pretty_string(999) .to_pretty_string(999)
), ),
}), }),
TraceLevel::Compact => Some(TypedExpr::String { TraceLevel::Compact | TraceLevel::Silent => None,
location,
tipo: string(),
value: self
.lines
.line_and_column_number(location.start)
.expect("Spans are within bounds.")
.to_string(),
}),
TraceLevel::Silent => None,
}; };
let typed_value = self.infer(value)?; let typed_value = self.infer(value)?;
self.unify(bool(), typed_value.tipo(), typed_value.location(), false)?; self.unify(bool(), typed_value.tipo(), typed_value.location(), false)?;
match self.tracing.trace_level(false) { match text {
TraceLevel::Silent => Ok(typed_value), None => Ok(typed_value),
TraceLevel::Verbose | TraceLevel::Compact => Ok(TypedExpr::If { Some(text) => Ok(TypedExpr::If {
location, location,
branches: vec1::vec1![IfBranch { branches: vec1::vec1![IfBranch {
condition: typed_value, condition: typed_value,
@ -710,7 +691,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
final_else: Box::new(TypedExpr::Trace { final_else: Box::new(TypedExpr::Trace {
location, location,
tipo: bool(), tipo: bool(),
text: Box::new(text.expect("TraceLevel::Silent excluded from pattern-guard")), text: Box::new(text),
then: Box::new(var_false), then: Box::new(var_false),
}), }),
tipo: bool(), tipo: bool(),
@ -2426,29 +2407,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
label: UntypedExpr, label: UntypedExpr,
arguments: Vec<UntypedExpr>, arguments: Vec<UntypedExpr>,
) -> Result<TypedExpr, Error> { ) -> Result<TypedExpr, Error> {
let label = self.infer_trace_arg(label)?;
let typed_arguments = arguments let typed_arguments = arguments
.into_iter() .into_iter()
.map(|arg| self.infer_trace_arg(arg)) .map(|arg| self.infer_trace_arg(arg))
.collect::<Result<Vec<_>, Error>>()?; .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 then = self.infer(then)?;
let tipo = then.tipo(); let tipo = then.tipo();
@ -2461,26 +2424,42 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
match self.tracing.trace_level(false) { match self.tracing.trace_level(false) {
TraceLevel::Silent => Ok(then), TraceLevel::Silent => Ok(then),
TraceLevel::Compact => Ok(TypedExpr::Trace { TraceLevel::Compact => {
location, let text = self.infer(label)?;
tipo, self.unify(string(), text.tipo(), text.location(), false)?;
then: Box::new(then), Ok(TypedExpr::Trace {
text: Box::new(TypedExpr::String {
location, location,
tipo: string(), tipo,
value: self then: Box::new(then),
.lines text: Box::new(text),
.line_and_column_number(location.start) })
.expect("Spans are within bounds.") }
.to_string(), TraceLevel::Verbose => {
}), let label = self.infer_trace_arg(label)?;
}),
TraceLevel::Verbose => Ok(TypedExpr::Trace { let text = if typed_arguments.is_empty() {
location, label
tipo, } else {
then: Box::new(then), let delimiter = |ix| TypedExpr::String {
text: Box::new(text), 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,
builtins::{fuzzer, generic_var}, builtins::{fuzzer, generic_var},
line_numbers::LineNumbers,
tipo::{expr::infer_function, Span, Type, TypeVar}, tipo::{expr::infer_function, Span, Type, TypeVar},
IdGenerator, IdGenerator,
}; };
@ -86,14 +85,8 @@ impl UntypedModule {
} }
for def in consts.into_iter().chain(not_consts) { for def in consts.into_iter().chain(not_consts) {
let definition = infer_definition( let definition =
def, infer_definition(def, &module_name, &mut hydrators, &mut environment, tracing)?;
&module_name,
&mut hydrators,
&mut environment,
&self.lines,
tracing,
)?;
definitions.push(definition); definitions.push(definition);
} }
@ -162,7 +155,6 @@ fn infer_definition(
module_name: &String, module_name: &String,
hydrators: &mut HashMap<String, Hydrator>, hydrators: &mut HashMap<String, Hydrator>,
environment: &mut Environment<'_>, environment: &mut Environment<'_>,
lines: &LineNumbers,
tracing: Tracing, tracing: Tracing,
) -> Result<TypedDefinition, Error> { ) -> Result<TypedDefinition, Error> {
match def { match def {
@ -171,7 +163,6 @@ fn infer_definition(
module_name, module_name,
hydrators, hydrators,
environment, environment,
lines,
tracing, tracing,
)?)), )?)),
@ -228,7 +219,7 @@ fn infer_definition(
} }
let mut typed_fun = 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() { if !typed_fun.return_type.is_bool() {
return Err(Error::ValidatorMustReturnBool { return Err(Error::ValidatorMustReturnBool {
@ -267,14 +258,8 @@ fn infer_definition(
let params = params.into_iter().chain(other.arguments); let params = params.into_iter().chain(other.arguments);
other.arguments = params.collect(); other.arguments = params.collect();
let mut other_typed_fun = infer_function( let mut other_typed_fun =
&other, infer_function(&other, module_name, hydrators, environment, tracing)?;
module_name,
hydrators,
environment,
lines,
tracing,
)?;
if !other_typed_fun.return_type.is_bool() { if !other_typed_fun.return_type.is_bool() {
return Err(Error::ValidatorMustReturnBool { return Err(Error::ValidatorMustReturnBool {
@ -338,8 +323,7 @@ fn infer_definition(
}); });
} }
let typed_via = let typed_via = ExprTyper::new(environment, tracing).infer(arg.via.clone())?;
ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?;
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap(); let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
@ -404,14 +388,7 @@ fn infer_definition(
None => Ok((None, None)), None => Ok((None, None)),
}?; }?;
let typed_f = infer_function( let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?;
&f.into(),
module_name,
hydrators,
environment,
lines,
tracing,
)?;
environment.unify( environment.unify(
typed_f.return_type.clone(), typed_f.return_type.clone(),
@ -629,7 +606,7 @@ fn infer_definition(
tipo: _, tipo: _,
}) => { }) => {
let typed_expr = 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(); let tipo = typed_expr.tipo();