Implement TraceIfFalse type-checking and AST transformation.

This caused me some trouble. In my first approach, I ended up having
  multiple traces because nested values would be evaluated twice; once
  as condition, and once as part of the continuation.

  To prevent this, we can simply evaluate the condition once, and return
  plain True / False boolean as outcome. So this effectively transforms any
  expression:

  ```
  expr
  ```

  as

  ```
  if expr { True } else { trace("...", False) }
  ```
This commit is contained in:
KtorZ 2023-02-16 13:53:05 +01:00 committed by Lucas
parent 6a50bde666
commit e9e3f4f50a
6 changed files with 130 additions and 23 deletions

View File

@ -1017,6 +1017,12 @@ pub enum TraceKind {
Error, Error,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tracing {
NoTraces,
KeepTraces,
}
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub struct Span { pub struct Span {
pub start: usize, pub start: usize,

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
ast::{ModuleKind, TypedModule, UntypedModule}, ast::{ModuleKind, Tracing, TypedModule, UntypedModule},
builtins, parser, builtins, parser,
tipo::error::{Error, Warning}, tipo::error::{Error, Warning},
IdGenerator, IdGenerator,
@ -24,7 +24,14 @@ fn check_module(
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
let result = ast.infer(&id_gen, kind, "test/project", &module_types, &mut warnings); let result = ast.infer(
&id_gen,
kind,
"test/project",
&module_types,
Tracing::KeepTraces,
&mut warnings,
);
result result
.map(|o| (warnings.clone(), o)) .map(|o| (warnings.clone(), o))

View File

@ -5,13 +5,14 @@ use vec1::Vec1;
use crate::{ use crate::{
ast::{ ast::{
Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant, Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant,
RecordUpdateSpread, Span, TraceKind, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, IfBranch, RecordUpdateSpread, Span, TraceKind, Tracing, TypedArg, TypedCallArg,
TypedConstant, TypedIfBranch, TypedMultiPattern, TypedRecordUpdateArg, UnOp, UntypedArg, TypedClause, TypedClauseGuard, TypedConstant, TypedIfBranch, TypedMultiPattern,
UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedIfBranch, UntypedMultiPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedConstant,
UntypedPattern, UntypedRecordUpdateArg, UntypedIfBranch, UntypedMultiPattern, UntypedPattern, UntypedRecordUpdateArg,
}, },
builtins::{bool, byte_array, function, int, list, string, tuple}, builtins::{bool, byte_array, function, int, list, string, tuple},
expr::{TypedExpr, UntypedExpr}, expr::{TypedExpr, UntypedExpr},
format,
tipo::fields::FieldMap, tipo::fields::FieldMap,
}; };
@ -29,6 +30,10 @@ use super::{
pub(crate) struct ExprTyper<'a, 'b> { pub(crate) struct ExprTyper<'a, 'b> {
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
// typed AST depending on this setting.
pub(crate) tracing: Tracing,
// Type hydrator for creating types from annotations // Type hydrator for creating types from annotations
pub(crate) hydrator: Hydrator, pub(crate) hydrator: Hydrator,
@ -377,6 +382,78 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
} }
} }
fn infer_trace_if_false(
&mut self,
value: UntypedExpr,
location: Span,
) -> Result<TypedExpr, Error> {
let var_true = TypedExpr::Var {
location,
name: "True".to_string(),
constructor: ValueConstructor {
public: true,
variant: ValueConstructorVariant::Record {
name: "True".to_string(),
arity: 0,
field_map: None,
location: Span::empty(),
module: String::new(),
constructors_count: 2,
},
tipo: bool(),
},
};
let var_false = TypedExpr::Var {
location,
name: "False".to_string(),
constructor: ValueConstructor {
public: true,
variant: ValueConstructorVariant::Record {
name: "False".to_string(),
arity: 0,
field_map: None,
location: Span::empty(),
module: String::new(),
constructors_count: 2,
},
tipo: bool(),
},
};
let text = TypedExpr::String {
location,
tipo: string(),
value: format!(
"{} ? False",
format::Formatter::new().expr(&value).to_pretty_string(999)
),
};
let typed_value = self.infer(value)?;
self.unify(typed_value.tipo(), bool(), typed_value.location(), false)?;
match self.tracing {
Tracing::NoTraces => Ok(typed_value),
Tracing::KeepTraces => Ok(TypedExpr::If {
location,
branches: vec1::vec1![IfBranch {
condition: typed_value,
body: var_true,
location,
}],
final_else: Box::new(TypedExpr::Trace {
location,
tipo: bool(),
text: Box::new(text),
then: Box::new(var_false),
}),
tipo: bool(),
}),
}
}
fn infer_binop( fn infer_binop(
&mut self, &mut self,
name: BinOp, name: BinOp,
@ -1879,12 +1956,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}) })
} }
Ok(TypedExpr::Trace { match self.tracing {
location, Tracing::NoTraces => Ok(then),
tipo, Tracing::KeepTraces => Ok(TypedExpr::Trace {
then: Box::new(then), location,
text: Box::new(text), tipo,
}) then: Box::new(then),
text: Box::new(text),
}),
}
} }
fn infer_value_constructor( fn infer_value_constructor(
@ -2058,7 +2138,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
self.environment.instantiate(t, ids, &self.hydrator) self.environment.instantiate(t, ids, &self.hydrator)
} }
pub fn new(environment: &'a mut Environment<'b>) -> Self { pub fn new(environment: &'a mut Environment<'b>, tracing: Tracing) -> Self {
let mut hydrator = Hydrator::new(); let mut hydrator = Hydrator::new();
hydrator.permit_holes(true); hydrator.permit_holes(true);
@ -2066,6 +2146,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Self { Self {
hydrator, hydrator,
environment, environment,
tracing,
ungeneralised_function_used: false, ungeneralised_function_used: false,
} }
} }

View File

@ -3,8 +3,8 @@ use std::collections::HashMap;
use crate::{ use crate::{
ast::{ ast::{
DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor, DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor,
RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition, RecordConstructorArg, Span, Tracing, TypeAlias, TypedDefinition, TypedModule,
UntypedModule, Use, Validator, UntypedDefinition, UntypedModule, Use, Validator,
}, },
builtins, builtins,
builtins::function, builtins::function,
@ -29,6 +29,7 @@ impl UntypedModule {
kind: ModuleKind, kind: ModuleKind,
package: &str, package: &str,
modules: &HashMap<String, TypeInfo>, modules: &HashMap<String, TypeInfo>,
tracing: Tracing,
warnings: &mut Vec<Warning>, warnings: &mut Vec<Warning>,
) -> Result<TypedModule, Error> { ) -> Result<TypedModule, Error> {
let name = self.name.clone(); let name = self.name.clone();
@ -83,7 +84,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(def, &name, &mut hydrators, &mut environment, kind)?; let definition =
infer_definition(def, &name, &mut hydrators, &mut environment, tracing, kind)?;
definitions.push(definition); definitions.push(definition);
} }
@ -148,6 +150,7 @@ 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<'_>,
tracing: Tracing,
kind: ModuleKind, kind: ModuleKind,
) -> Result<TypedDefinition, Error> { ) -> Result<TypedDefinition, Error> {
match def { match def {
@ -192,7 +195,7 @@ fn infer_definition(
.map(|(arg_name, tipo)| arg_name.set_type(tipo.clone())) .map(|(arg_name, tipo)| arg_name.set_type(tipo.clone()))
.collect(); .collect();
let mut expr_typer = ExprTyper::new(environment); let mut expr_typer = ExprTyper::new(environment, tracing);
expr_typer.hydrator = hydrators expr_typer.hydrator = hydrators
.remove(&name) .remove(&name)
@ -266,6 +269,7 @@ fn infer_definition(
module_name, module_name,
hydrators, hydrators,
environment, environment,
tracing,
kind, kind,
)? { )? {
if !typed_fun.return_type.is_bool() { if !typed_fun.return_type.is_bool() {
@ -297,9 +301,14 @@ fn infer_definition(
} }
Definition::Test(f) => { Definition::Test(f) => {
if let Definition::Fn(f) = if let Definition::Fn(f) = infer_definition(
infer_definition(Definition::Fn(f), module_name, hydrators, environment, kind)? Definition::Fn(f),
{ module_name,
hydrators,
environment,
tracing,
kind,
)? {
environment.unify(f.return_type.clone(), builtins::bool(), f.location, false)?; environment.unify(f.return_type.clone(), builtins::bool(), f.location, false)?;
Ok(Definition::Test(f)) Ok(Definition::Test(f))
@ -505,7 +514,8 @@ fn infer_definition(
}) })
} }
let typed_expr = ExprTyper::new(environment).infer_const(&annotation, *value)?; let typed_expr =
ExprTyper::new(environment, tracing).infer_const(&annotation, *value)?;
let tipo = typed_expr.tipo(); let tipo = typed_expr.tipo();

View File

@ -177,7 +177,7 @@ mod test {
use crate::{module::ParsedModule, PackageName}; use crate::{module::ParsedModule, PackageName};
use aiken_lang::{ use aiken_lang::{
self, self,
ast::{ModuleKind, TypedDataType, TypedFunction}, ast::{ModuleKind, Tracing, TypedDataType, TypedFunction},
builder::{DataTypeKey, FunctionAccessKey}, builder::{DataTypeKey, FunctionAccessKey},
builtins, parser, builtins, parser,
tipo::TypeInfo, tipo::TypeInfo,
@ -253,6 +253,7 @@ mod test {
module.kind, module.kind,
&self.package.to_string(), &self.package.to_string(),
&self.module_types, &self.module_types,
Tracing::NoTraces,
&mut warnings, &mut warnings,
) )
.expect("Failed to type-check module"); .expect("Failed to type-check module");

View File

@ -14,7 +14,7 @@ pub mod telemetry;
use crate::blueprint::{schema::Schema, Blueprint}; use crate::blueprint::{schema::Schema, Blueprint};
use aiken_lang::{ use aiken_lang::{
ast::{Definition, Function, ModuleKind, TypedDataType, TypedFunction}, ast::{Definition, Function, ModuleKind, Tracing, TypedDataType, TypedFunction},
builder::{DataTypeKey, FunctionAccessKey}, builder::{DataTypeKey, FunctionAccessKey},
builtins, builtins,
tipo::TypeInfo, tipo::TypeInfo,
@ -496,6 +496,8 @@ where
kind, kind,
&self.config.name.to_string(), &self.config.name.to_string(),
&self.module_types, &self.module_types,
// TODO: Make configurable
Tracing::KeepTraces,
&mut type_warnings, &mut type_warnings,
) )
.map_err(|error| Error::Type { .map_err(|error| Error::Type {