1007 lines
34 KiB
Rust
1007 lines
34 KiB
Rust
use super::{
|
|
environment::{EntityKind, Environment},
|
|
error::{Error, UnifyErrorSituation, Warning},
|
|
expr::ExprTyper,
|
|
hydrator::Hydrator,
|
|
TypeInfo, ValueConstructor, ValueConstructorVariant,
|
|
};
|
|
use crate::{
|
|
ast::{
|
|
Annotation, ArgBy, ArgName, ArgVia, DataType, Definition, Function, ModuleConstant,
|
|
ModuleKind, RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedArg,
|
|
TypedDefinition, TypedModule, TypedValidator, UntypedArg, UntypedDefinition, UntypedModule,
|
|
UntypedPattern, UntypedValidator, Use, Validator,
|
|
},
|
|
expr::{TypedExpr, UntypedAssignmentKind, UntypedExpr},
|
|
parser::token::Token,
|
|
tipo::{expr::infer_function, Span, Type, TypeVar},
|
|
IdGenerator,
|
|
};
|
|
use std::{
|
|
borrow::Borrow,
|
|
collections::{BTreeSet, HashMap},
|
|
ops::Deref,
|
|
rc::Rc,
|
|
};
|
|
|
|
impl UntypedModule {
|
|
#[allow(clippy::too_many_arguments)]
|
|
#[allow(clippy::result_large_err)]
|
|
pub fn infer(
|
|
mut self,
|
|
id_gen: &IdGenerator,
|
|
kind: ModuleKind,
|
|
package: &str,
|
|
modules: &HashMap<String, TypeInfo>,
|
|
tracing: Tracing,
|
|
warnings: &mut Vec<Warning>,
|
|
env: Option<&str>,
|
|
) -> Result<TypedModule, Error> {
|
|
let module_name = self.name.clone();
|
|
let docs = std::mem::take(&mut self.docs);
|
|
let mut environment =
|
|
Environment::new(id_gen.clone(), &module_name, &kind, modules, warnings, env);
|
|
|
|
let mut type_names = HashMap::with_capacity(self.definitions.len());
|
|
let mut value_names = HashMap::with_capacity(self.definitions.len());
|
|
let mut hydrators = HashMap::with_capacity(self.definitions.len());
|
|
|
|
// Register any modules, types, and values being imported
|
|
// We process imports first so that anything imported can be referenced
|
|
// anywhere in the module.
|
|
for def in self.definitions() {
|
|
environment.register_import(def)?;
|
|
}
|
|
|
|
// Register types so they can be used in constructors and functions
|
|
// earlier in the module.
|
|
environment.register_types(
|
|
self.definitions.iter().collect(),
|
|
&module_name,
|
|
&mut hydrators,
|
|
&mut type_names,
|
|
)?;
|
|
|
|
// Register values so they can be used in functions earlier in the module.
|
|
for def in self.definitions() {
|
|
environment.register_values(
|
|
def,
|
|
&module_name,
|
|
&mut hydrators,
|
|
&mut value_names,
|
|
kind,
|
|
)?;
|
|
}
|
|
|
|
// Infer the types of each definition in the module
|
|
// We first infer all the constants so they can be used in functions defined
|
|
// anywhere in the module.
|
|
let mut definitions = Vec::with_capacity(self.definitions.len());
|
|
let mut consts = vec![];
|
|
let mut not_consts = vec![];
|
|
|
|
for def in self.definitions().cloned() {
|
|
match def {
|
|
Definition::ModuleConstant { .. } => consts.push(def),
|
|
Definition::Validator { .. } if kind.is_validator() => not_consts.push(def),
|
|
Definition::Validator { .. } => (),
|
|
Definition::Fn { .. }
|
|
| Definition::Test { .. }
|
|
| Definition::Benchmark { .. }
|
|
| Definition::TypeAlias { .. }
|
|
| Definition::DataType { .. }
|
|
| Definition::Use { .. } => not_consts.push(def),
|
|
}
|
|
}
|
|
|
|
for def in consts.into_iter().chain(not_consts) {
|
|
let definition =
|
|
infer_definition(def, &module_name, &mut hydrators, &mut environment, tracing)?;
|
|
|
|
definitions.push(definition);
|
|
}
|
|
|
|
// Generalise functions now that the entire module has been inferred
|
|
let definitions = definitions
|
|
.into_iter()
|
|
.map(|def| environment.generalise_definition(def, &module_name))
|
|
.collect();
|
|
|
|
// Generate warnings for unused items
|
|
environment.warnings.retain(|warning| match warning {
|
|
Warning::UnusedVariable { location, name } => !environment
|
|
.validator_params
|
|
.contains(&(name.to_string(), *location)),
|
|
_ => true,
|
|
});
|
|
environment.convert_unused_to_warnings();
|
|
|
|
// Remove private and imported types and values to create the public interface
|
|
environment
|
|
.module_types
|
|
.retain(|_, info| info.public && info.module == module_name);
|
|
|
|
let own_types = environment.module_types.keys().collect::<BTreeSet<_>>();
|
|
|
|
environment.module_values.retain(|_, info| info.public);
|
|
|
|
environment
|
|
.module_types_constructors
|
|
.retain(|k, _| own_types.contains(k));
|
|
|
|
environment
|
|
.accessors
|
|
.retain(|_, accessors| accessors.public);
|
|
|
|
// Ensure no exported values have private types in their type signature
|
|
for value in environment.module_values.values() {
|
|
if let Some(leaked) = value.tipo.find_private_type() {
|
|
return Err(Error::PrivateTypeLeak {
|
|
location: value.variant.location(),
|
|
leaked,
|
|
});
|
|
}
|
|
}
|
|
|
|
let Environment {
|
|
module_types: types,
|
|
module_types_constructors: types_constructors,
|
|
module_values: values,
|
|
accessors,
|
|
annotations,
|
|
..
|
|
} = environment;
|
|
|
|
Ok(TypedModule {
|
|
docs,
|
|
name: module_name.clone(),
|
|
definitions,
|
|
kind,
|
|
lines: self.lines,
|
|
type_info: TypeInfo {
|
|
name: module_name,
|
|
types,
|
|
types_constructors,
|
|
values,
|
|
accessors,
|
|
annotations,
|
|
kind,
|
|
package: package.to_string(),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
fn infer_definition(
|
|
def: UntypedDefinition,
|
|
module_name: &String,
|
|
hydrators: &mut HashMap<String, Hydrator>,
|
|
environment: &mut Environment<'_>,
|
|
tracing: Tracing,
|
|
) -> Result<TypedDefinition, Error> {
|
|
match def {
|
|
Definition::Fn(f) => Ok(Definition::Fn(infer_function(
|
|
&f,
|
|
module_name,
|
|
hydrators,
|
|
environment,
|
|
tracing,
|
|
)?)),
|
|
|
|
Definition::Validator(Validator {
|
|
doc,
|
|
location,
|
|
end_position,
|
|
handlers,
|
|
mut fallback,
|
|
params,
|
|
name,
|
|
}) => {
|
|
let params_length = params.len();
|
|
|
|
environment.in_new_scope(|environment| {
|
|
let fallback_name = TypedValidator::handler_name(&name, &fallback.name);
|
|
|
|
put_params_in_scope(&fallback_name, environment, ¶ms);
|
|
|
|
let mut typed_handlers = vec![];
|
|
|
|
for mut handler in handlers {
|
|
let typed_fun = environment.in_new_scope(|environment| {
|
|
let temp_params = params.iter().cloned().chain(handler.arguments);
|
|
handler.arguments = temp_params.collect();
|
|
|
|
let handler_name = TypedValidator::handler_name(&name, &handler.name);
|
|
|
|
let old_name = handler.name;
|
|
handler.name = handler_name;
|
|
|
|
let mut typed_fun =
|
|
infer_function(&handler, module_name, hydrators, environment, tracing)?;
|
|
|
|
typed_fun.name = old_name;
|
|
|
|
if !typed_fun.return_type.is_bool() {
|
|
return Err(Error::ValidatorMustReturnBool {
|
|
return_type: typed_fun.return_type.clone(),
|
|
location: typed_fun.location,
|
|
});
|
|
}
|
|
|
|
typed_fun.arguments.drain(0..params_length);
|
|
|
|
if !typed_fun.has_valid_purpose_name() {
|
|
return Err(Error::UnknownPurpose {
|
|
location: typed_fun
|
|
.location
|
|
.map(|start, _end| (start, start + typed_fun.name.len())),
|
|
available_purposes: TypedValidator::available_handler_names(),
|
|
});
|
|
}
|
|
|
|
if typed_fun.arguments.len() != typed_fun.validator_arity() {
|
|
return Err(Error::IncorrectValidatorArity {
|
|
count: typed_fun.arguments.len() as u32,
|
|
expected: typed_fun.validator_arity() as u32,
|
|
location: typed_fun.location,
|
|
});
|
|
}
|
|
|
|
if typed_fun.is_spend() && !typed_fun.arguments[0].tipo.is_option() {
|
|
return Err(Error::CouldNotUnify {
|
|
location: typed_fun.arguments[0].location,
|
|
expected: Type::option(typed_fun.arguments[0].tipo.clone()),
|
|
given: typed_fun.arguments[0].tipo.clone(),
|
|
situation: None,
|
|
rigid_type_names: Default::default(),
|
|
});
|
|
}
|
|
|
|
for arg in typed_fun.arguments.iter_mut() {
|
|
if arg.tipo.is_unbound() {
|
|
arg.tipo = Type::data();
|
|
}
|
|
}
|
|
|
|
Ok(typed_fun)
|
|
})?;
|
|
|
|
typed_handlers.push(typed_fun);
|
|
}
|
|
|
|
// NOTE: Duplicates are handled when registering handler names. So if we have N
|
|
// typed handlers, they are different. The -1 represents takes out the fallback
|
|
// handler name.
|
|
let is_exhaustive =
|
|
typed_handlers.len() >= TypedValidator::available_handler_names().len() - 1;
|
|
|
|
if is_exhaustive
|
|
&& fallback != UntypedValidator::default_fallback(fallback.location)
|
|
{
|
|
return Err(Error::UnexpectedValidatorFallback {
|
|
fallback: fallback.location,
|
|
});
|
|
}
|
|
|
|
let (typed_params, typed_fallback) = environment.in_new_scope(|environment| {
|
|
let temp_params = params.iter().cloned().chain(fallback.arguments);
|
|
fallback.arguments = temp_params.collect();
|
|
|
|
let old_name = fallback.name;
|
|
fallback.name = fallback_name;
|
|
|
|
let mut typed_fallback =
|
|
infer_function(&fallback, module_name, hydrators, environment, tracing)?;
|
|
|
|
typed_fallback.name = old_name;
|
|
|
|
if !typed_fallback.return_type.is_bool() {
|
|
return Err(Error::ValidatorMustReturnBool {
|
|
return_type: typed_fallback.return_type.clone(),
|
|
location: typed_fallback.location,
|
|
});
|
|
}
|
|
|
|
let typed_params = typed_fallback
|
|
.arguments
|
|
.drain(0..params_length)
|
|
.map(|mut arg| {
|
|
if arg.tipo.is_unbound() {
|
|
arg.tipo = Type::data();
|
|
}
|
|
|
|
arg
|
|
})
|
|
.collect();
|
|
|
|
if typed_fallback.arguments.len() != 1 {
|
|
return Err(Error::IncorrectValidatorArity {
|
|
count: typed_fallback.arguments.len() as u32,
|
|
expected: 1,
|
|
location: typed_fallback.location,
|
|
});
|
|
}
|
|
|
|
for arg in typed_fallback.arguments.iter_mut() {
|
|
if arg.tipo.is_unbound() {
|
|
arg.tipo = Type::data();
|
|
}
|
|
}
|
|
|
|
Ok((typed_params, typed_fallback))
|
|
})?;
|
|
|
|
Ok(Definition::Validator(Validator {
|
|
doc,
|
|
end_position,
|
|
handlers: typed_handlers,
|
|
fallback: typed_fallback,
|
|
name,
|
|
location,
|
|
params: typed_params,
|
|
}))
|
|
})
|
|
}
|
|
|
|
Definition::Test(f) => {
|
|
let (typed_via, annotation) = match f.arguments.first() {
|
|
Some(arg) => {
|
|
if f.arguments.len() > 1 {
|
|
return Err(Error::IncorrectTestArity {
|
|
count: f.arguments.len(),
|
|
location: f
|
|
.arguments
|
|
.get(1)
|
|
.expect("arguments.len() > 1")
|
|
.arg
|
|
.location,
|
|
});
|
|
}
|
|
|
|
extract_via_information(&f, arg, hydrators, environment, tracing, infer_fuzzer)
|
|
.map(|(typed_via, annotation)| (Some(typed_via), Some(annotation)))
|
|
}
|
|
None => Ok((None, None)),
|
|
}?;
|
|
|
|
let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?;
|
|
|
|
let is_bool = environment.unify(
|
|
typed_f.return_type.clone(),
|
|
Type::bool(),
|
|
typed_f.location,
|
|
false,
|
|
);
|
|
|
|
let is_void = environment.unify(
|
|
typed_f.return_type.clone(),
|
|
Type::void(),
|
|
typed_f.location,
|
|
false,
|
|
);
|
|
|
|
if is_bool.or(is_void).is_err() {
|
|
return Err(Error::IllegalTestType {
|
|
location: typed_f.location,
|
|
});
|
|
}
|
|
|
|
Ok(Definition::Test(Function {
|
|
doc: typed_f.doc,
|
|
location: typed_f.location,
|
|
name: typed_f.name,
|
|
public: typed_f.public,
|
|
arguments: match typed_via {
|
|
Some((via, tipo)) => {
|
|
let arg = typed_f
|
|
.arguments
|
|
.first()
|
|
.expect("has exactly one argument")
|
|
.to_owned();
|
|
vec![ArgVia {
|
|
arg: TypedArg {
|
|
tipo,
|
|
annotation,
|
|
..arg
|
|
},
|
|
via,
|
|
}]
|
|
}
|
|
None => vec![],
|
|
},
|
|
return_annotation: typed_f.return_annotation,
|
|
return_type: typed_f.return_type,
|
|
body: typed_f.body,
|
|
on_test_failure: typed_f.on_test_failure,
|
|
end_position: typed_f.end_position,
|
|
}))
|
|
}
|
|
|
|
Definition::Benchmark(f) => {
|
|
let err_incorrect_arity = || {
|
|
Err(Error::IncorrectBenchmarkArity {
|
|
location: f
|
|
.location
|
|
.map(|start, end| (start + Token::Benchmark.to_string().len() + 1, end)),
|
|
})
|
|
};
|
|
|
|
let (typed_via, annotation) = match f.arguments.first() {
|
|
None => return err_incorrect_arity(),
|
|
Some(arg) => {
|
|
if f.arguments.len() > 1 {
|
|
return err_incorrect_arity();
|
|
}
|
|
|
|
extract_via_information(&f, arg, hydrators, environment, tracing, infer_sampler)
|
|
}
|
|
}?;
|
|
|
|
let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?;
|
|
|
|
let arguments = {
|
|
let arg = typed_f
|
|
.arguments
|
|
.first()
|
|
.expect("has exactly one argument")
|
|
.to_owned();
|
|
|
|
vec![ArgVia {
|
|
arg: TypedArg {
|
|
tipo: typed_via.1,
|
|
annotation: Some(annotation),
|
|
..arg
|
|
},
|
|
via: typed_via.0,
|
|
}]
|
|
};
|
|
|
|
Ok(Definition::Benchmark(Function {
|
|
doc: typed_f.doc,
|
|
location: typed_f.location,
|
|
name: typed_f.name,
|
|
public: typed_f.public,
|
|
arguments,
|
|
return_annotation: typed_f.return_annotation,
|
|
return_type: typed_f.return_type,
|
|
body: typed_f.body,
|
|
on_test_failure: typed_f.on_test_failure,
|
|
end_position: typed_f.end_position,
|
|
}))
|
|
}
|
|
|
|
Definition::TypeAlias(TypeAlias {
|
|
doc,
|
|
location,
|
|
public,
|
|
alias,
|
|
parameters,
|
|
annotation,
|
|
tipo: _,
|
|
}) => {
|
|
let tipo = environment
|
|
.get_type_constructor(&None, &alias, location)
|
|
.expect("Could not find existing type for type alias")
|
|
.tipo
|
|
.clone();
|
|
|
|
Ok(Definition::TypeAlias(TypeAlias {
|
|
doc,
|
|
location,
|
|
public,
|
|
alias,
|
|
parameters,
|
|
annotation,
|
|
tipo,
|
|
}))
|
|
}
|
|
|
|
Definition::DataType(DataType {
|
|
doc,
|
|
location,
|
|
public,
|
|
opaque,
|
|
name,
|
|
parameters,
|
|
constructors: untyped_constructors,
|
|
typed_parameters: _,
|
|
}) => {
|
|
let constructors = untyped_constructors
|
|
.into_iter()
|
|
.map(|constructor| {
|
|
let preregistered_fn = environment
|
|
.get_variable(&constructor.name)
|
|
.expect("Could not find preregistered type for function");
|
|
|
|
let preregistered_type = preregistered_fn.tipo.clone();
|
|
|
|
let args = preregistered_type.function_types().map_or(
|
|
Ok(vec![]),
|
|
|(args_types, _return_type)| {
|
|
constructor
|
|
.arguments
|
|
.into_iter()
|
|
.zip(&args_types)
|
|
.map(|(arg, t)| {
|
|
if t.is_function() {
|
|
return Err(Error::FunctionTypeInData {
|
|
location: arg.location,
|
|
});
|
|
}
|
|
|
|
if t.is_ml_result() {
|
|
return Err(Error::IllegalTypeInData {
|
|
location: arg.location,
|
|
tipo: t.clone(),
|
|
});
|
|
}
|
|
|
|
if t.contains_opaque() {
|
|
let parent = environment
|
|
.get_type_constructor_mut(&name, location)?;
|
|
|
|
Rc::make_mut(&mut parent.tipo).set_opaque(true)
|
|
}
|
|
|
|
Ok(RecordConstructorArg {
|
|
label: arg.label,
|
|
annotation: arg.annotation,
|
|
location: arg.location,
|
|
doc: arg.doc,
|
|
tipo: t.clone(),
|
|
})
|
|
})
|
|
.collect()
|
|
},
|
|
)?;
|
|
|
|
Ok(RecordConstructor {
|
|
location: constructor.location,
|
|
name: constructor.name,
|
|
arguments: args,
|
|
doc: constructor.doc,
|
|
sugar: constructor.sugar,
|
|
})
|
|
})
|
|
.collect::<Result<_, Error>>()?;
|
|
|
|
let typed_parameters = environment
|
|
.get_type_constructor(&None, &name, location)
|
|
.expect("Could not find preregistered type constructor ")
|
|
.parameters
|
|
.clone();
|
|
|
|
let typed_data = DataType {
|
|
doc,
|
|
location,
|
|
public,
|
|
opaque,
|
|
name,
|
|
parameters,
|
|
constructors,
|
|
typed_parameters,
|
|
};
|
|
|
|
for constr in &typed_data.constructors {
|
|
for RecordConstructorArg {
|
|
tipo,
|
|
location,
|
|
doc: _,
|
|
label: _,
|
|
annotation: _,
|
|
} in &constr.arguments
|
|
{
|
|
if tipo.is_function() {
|
|
return Err(Error::FunctionTypeInData {
|
|
location: *location,
|
|
});
|
|
}
|
|
|
|
if tipo.is_ml_result() {
|
|
return Err(Error::IllegalTypeInData {
|
|
location: *location,
|
|
tipo: tipo.clone(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(Definition::DataType(typed_data))
|
|
}
|
|
|
|
Definition::Use(Use {
|
|
location,
|
|
module,
|
|
as_name,
|
|
unqualified,
|
|
package: _,
|
|
}) => {
|
|
let module_info = environment.find_module(&module, location)?;
|
|
|
|
Ok(Definition::Use(Use {
|
|
location,
|
|
module,
|
|
as_name,
|
|
unqualified,
|
|
package: module_info.package.clone(),
|
|
}))
|
|
}
|
|
|
|
Definition::ModuleConstant(ModuleConstant {
|
|
doc,
|
|
location,
|
|
name,
|
|
annotation,
|
|
public,
|
|
value,
|
|
}) => {
|
|
let typed_assignment = ExprTyper::new(environment, tracing).infer_assignment(
|
|
UntypedPattern::Var {
|
|
location,
|
|
name: name.clone(),
|
|
},
|
|
value,
|
|
UntypedAssignmentKind::Let { backpassing: false },
|
|
&annotation,
|
|
location,
|
|
)?;
|
|
|
|
// NOTE: The assignment above is only a convenient way to create the TypedExpression
|
|
// that will be reduced at compile-time. We must increment its usage to not
|
|
// automatically trigger a warning since we are virtually creating a block with a
|
|
// single assignment that is then left unused.
|
|
//
|
|
// The usage of the constant is tracked through different means.
|
|
environment.increment_usage(&name);
|
|
|
|
let typed_expr = match typed_assignment {
|
|
TypedExpr::Assignment { value, .. } => value,
|
|
_ => unreachable!("infer_assignment inferred something else than an assignment?"),
|
|
};
|
|
|
|
let tipo = typed_expr.tipo();
|
|
|
|
if tipo.is_function() && !tipo.is_monomorphic() {
|
|
return Err(Error::GenericLeftAtBoundary { location });
|
|
}
|
|
|
|
let variant = ValueConstructor {
|
|
public,
|
|
variant: ValueConstructorVariant::ModuleConstant {
|
|
location,
|
|
name: name.to_owned(),
|
|
module: module_name.to_owned(),
|
|
},
|
|
tipo: tipo.clone(),
|
|
};
|
|
|
|
environment.insert_variable(name.clone(), variant.variant.clone(), tipo.clone());
|
|
|
|
environment.insert_module_value(&name, variant);
|
|
|
|
if !public {
|
|
environment.init_usage(name.clone(), EntityKind::PrivateConstant, location);
|
|
}
|
|
|
|
Ok(Definition::ModuleConstant(ModuleConstant {
|
|
doc,
|
|
location,
|
|
name,
|
|
annotation,
|
|
public,
|
|
value: *typed_expr,
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
fn extract_via_information<F>(
|
|
f: &Function<(), UntypedExpr, ArgVia<UntypedArg, UntypedExpr>>,
|
|
arg: &ArgVia<UntypedArg, UntypedExpr>,
|
|
hydrators: &mut HashMap<String, Hydrator>,
|
|
environment: &mut Environment<'_>,
|
|
tracing: Tracing,
|
|
infer_via: F,
|
|
) -> Result<((TypedExpr, Rc<Type>), Annotation), Error>
|
|
where
|
|
F: FnOnce(
|
|
&mut Environment<'_>,
|
|
Option<Rc<Type>>,
|
|
&Rc<Type>,
|
|
&Span,
|
|
) -> Result<(Annotation, Rc<Type>), Error>,
|
|
{
|
|
let typed_via = ExprTyper::new(environment, tracing).infer(arg.via.clone())?;
|
|
|
|
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
|
|
|
|
let provided_inner_type = arg
|
|
.arg
|
|
.annotation
|
|
.as_ref()
|
|
.map(|ann| hydrator.type_from_annotation(ann, environment))
|
|
.transpose()?;
|
|
|
|
let (inferred_annotation, inferred_inner_type) = infer_via(
|
|
environment,
|
|
provided_inner_type.clone(),
|
|
&typed_via.tipo(),
|
|
&arg.via.location(),
|
|
)?;
|
|
|
|
// Ensure that the annotation, if any, matches the type inferred from the
|
|
// Fuzzer.
|
|
if let Some(provided_inner_type) = provided_inner_type {
|
|
if !arg
|
|
.arg
|
|
.annotation
|
|
.as_ref()
|
|
.unwrap()
|
|
.is_logically_equal(&inferred_annotation)
|
|
{
|
|
return Err(Error::CouldNotUnify {
|
|
location: arg.arg.location,
|
|
expected: inferred_inner_type.clone(),
|
|
given: provided_inner_type.clone(),
|
|
situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch),
|
|
rigid_type_names: hydrator.rigid_names(),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Replace the pre-registered type for the test function, to allow inferring
|
|
// the function body with the right type arguments.
|
|
let scope = environment
|
|
.scope
|
|
.get_mut(&f.name)
|
|
.expect("Could not find preregistered type for test");
|
|
if let Type::Fn {
|
|
ref ret,
|
|
ref alias,
|
|
args: _,
|
|
} = scope.tipo.as_ref()
|
|
{
|
|
scope.tipo = Rc::new(Type::Fn {
|
|
ret: ret.clone(),
|
|
args: vec![inferred_inner_type.clone()],
|
|
alias: alias.clone(),
|
|
})
|
|
}
|
|
|
|
Ok(((typed_via, inferred_inner_type), inferred_annotation))
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
fn infer_fuzzer(
|
|
environment: &mut Environment<'_>,
|
|
expected_inner_type: Option<Rc<Type>>,
|
|
tipo: &Rc<Type>,
|
|
location: &Span,
|
|
) -> Result<(Annotation, Rc<Type>), Error> {
|
|
let could_not_unify = || Error::CouldNotUnify {
|
|
location: *location,
|
|
expected: Type::fuzzer(
|
|
expected_inner_type
|
|
.clone()
|
|
.unwrap_or_else(|| Type::generic_var(0)),
|
|
),
|
|
given: tipo.clone(),
|
|
situation: None,
|
|
rigid_type_names: HashMap::new(),
|
|
};
|
|
|
|
match tipo.borrow() {
|
|
Type::Fn {
|
|
ret,
|
|
args: _,
|
|
alias: _,
|
|
} => match ret.borrow() {
|
|
Type::App {
|
|
module,
|
|
name,
|
|
args,
|
|
public: _,
|
|
contains_opaque: _,
|
|
alias: _,
|
|
} if module.is_empty() && name == "Option" && args.len() == 1 => {
|
|
match args.first().expect("args.len() == 1").borrow() {
|
|
Type::Tuple { elems, .. } if elems.len() == 2 => {
|
|
let wrapped = elems.get(1).expect("Tuple has two elements");
|
|
|
|
// NOTE: Although we've drilled through the Fuzzer structure to get here,
|
|
// we still need to enforce that:
|
|
//
|
|
// 1. The Fuzzer is a function with a single argument of type PRNG
|
|
// 2. It returns not only a wrapped type, but also a new PRNG
|
|
//
|
|
// All-in-all, we could bundle those verification through the
|
|
// `infer_fuzzer` function, but instead, we can also just piggyback on
|
|
// `unify` now that we have figured out the type carried by the fuzzer.
|
|
environment.unify(
|
|
tipo.clone(),
|
|
Type::fuzzer(wrapped.clone()),
|
|
*location,
|
|
false,
|
|
)?;
|
|
|
|
Ok((annotate_fuzzer(wrapped, location)?, wrapped.clone()))
|
|
}
|
|
_ => Err(could_not_unify()),
|
|
}
|
|
}
|
|
_ => Err(could_not_unify()),
|
|
},
|
|
|
|
Type::Var { tipo, alias } => match &*tipo.deref().borrow() {
|
|
TypeVar::Link { tipo } => infer_fuzzer(
|
|
environment,
|
|
expected_inner_type,
|
|
&Type::with_alias(tipo.clone(), alias.clone()),
|
|
location,
|
|
),
|
|
_ => Err(Error::GenericLeftAtBoundary {
|
|
location: *location,
|
|
}),
|
|
},
|
|
|
|
Type::App { .. } | Type::Tuple { .. } | Type::Pair { .. } => Err(could_not_unify()),
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
fn infer_sampler(
|
|
environment: &mut Environment<'_>,
|
|
expected_inner_type: Option<Rc<Type>>,
|
|
tipo: &Rc<Type>,
|
|
location: &Span,
|
|
) -> Result<(Annotation, Rc<Type>), Error> {
|
|
let could_not_unify = || Error::CouldNotUnify {
|
|
location: *location,
|
|
expected: Type::sampler(
|
|
expected_inner_type
|
|
.clone()
|
|
.unwrap_or_else(|| Type::generic_var(0)),
|
|
),
|
|
given: tipo.clone(),
|
|
situation: None,
|
|
rigid_type_names: HashMap::new(),
|
|
};
|
|
|
|
match tipo.borrow() {
|
|
Type::Fn {
|
|
ret,
|
|
args,
|
|
alias: _,
|
|
} => {
|
|
if args.len() == 1 && args[0].is_int() {
|
|
infer_fuzzer(environment, expected_inner_type, ret, &Span::empty())
|
|
} else {
|
|
Err(could_not_unify())
|
|
}
|
|
}
|
|
|
|
Type::Var { tipo, alias } => match &*tipo.deref().borrow() {
|
|
TypeVar::Link { tipo } => infer_sampler(
|
|
environment,
|
|
expected_inner_type,
|
|
&Type::with_alias(tipo.clone(), alias.clone()),
|
|
location,
|
|
),
|
|
_ => Err(Error::GenericLeftAtBoundary {
|
|
location: *location,
|
|
}),
|
|
},
|
|
|
|
Type::App { .. } | Type::Tuple { .. } | Type::Pair { .. } => Err(could_not_unify()),
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result<Annotation, Error> {
|
|
match tipo {
|
|
Type::App {
|
|
name,
|
|
module,
|
|
args,
|
|
public: _,
|
|
contains_opaque: _,
|
|
alias: _,
|
|
} => {
|
|
let arguments = args
|
|
.iter()
|
|
.map(|arg| annotate_fuzzer(arg, location))
|
|
.collect::<Result<Vec<Annotation>, _>>()?;
|
|
Ok(Annotation::Constructor {
|
|
name: name.to_owned(),
|
|
module: if module.is_empty() {
|
|
None
|
|
} else {
|
|
Some(module.to_owned())
|
|
},
|
|
arguments,
|
|
location: *location,
|
|
})
|
|
}
|
|
|
|
Type::Tuple { elems, alias: _ } => {
|
|
let elems = elems
|
|
.iter()
|
|
.map(|arg| annotate_fuzzer(arg, location))
|
|
.collect::<Result<Vec<Annotation>, _>>()?;
|
|
Ok(Annotation::Tuple {
|
|
elems,
|
|
location: *location,
|
|
})
|
|
}
|
|
|
|
Type::Var { tipo, alias: _ } => match &*tipo.deref().borrow() {
|
|
TypeVar::Link { tipo } => annotate_fuzzer(tipo, location),
|
|
_ => Err(Error::GenericLeftAtBoundary {
|
|
location: *location,
|
|
}),
|
|
},
|
|
Type::Fn { .. } => Err(Error::IllegalTypeInData {
|
|
location: *location,
|
|
tipo: Rc::new(tipo.clone()),
|
|
}),
|
|
Type::Pair { fst, snd, .. } => {
|
|
let fst = annotate_fuzzer(fst, location)?;
|
|
let snd = annotate_fuzzer(snd, location)?;
|
|
Ok(Annotation::Pair {
|
|
fst: Box::new(fst),
|
|
snd: Box::new(snd),
|
|
location: *location,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
fn put_params_in_scope<'a>(
|
|
name: &'_ str,
|
|
environment: &'a mut Environment,
|
|
params: &'a [UntypedArg],
|
|
) {
|
|
let preregistered_fn = environment
|
|
.get_variable(name)
|
|
.expect("Could not find preregistered type for function");
|
|
|
|
let preregistered_type = preregistered_fn.tipo.clone();
|
|
|
|
let (args_types, _return_type) = preregistered_type
|
|
.function_types()
|
|
.expect("Preregistered type for fn was not a fn");
|
|
|
|
for (ix, (arg, t)) in params
|
|
.iter()
|
|
.zip(args_types[0..params.len()].iter())
|
|
.enumerate()
|
|
{
|
|
match arg.arg_name(ix) {
|
|
ArgName::Named {
|
|
name,
|
|
label: _,
|
|
location: _,
|
|
} if arg.is_validator_param => {
|
|
environment.insert_variable(
|
|
name.to_string(),
|
|
ValueConstructorVariant::LocalVariable {
|
|
location: arg.location,
|
|
},
|
|
t.clone(),
|
|
);
|
|
|
|
if let ArgBy::ByPattern(ref pattern) = arg.by {
|
|
pattern.collect_identifiers(&mut |identifier| {
|
|
environment.validator_params.insert(identifier);
|
|
})
|
|
}
|
|
|
|
environment.init_usage(name, EntityKind::Variable, arg.location);
|
|
}
|
|
ArgName::Named { .. } | ArgName::Discarded { .. } => (),
|
|
};
|
|
}
|
|
}
|