diff --git a/src/can/def.rs b/src/can/def.rs index 20dcf85099..0b1ce93895 100644 --- a/src/can/def.rs +++ b/src/can/def.rs @@ -5,6 +5,7 @@ use crate::can::expr::{ canonicalize_expr, local_successors, references_from_call, references_from_local, union_pairs, Output, Recursive, }; +use crate::can::ident::Lowercase; use crate::can::pattern::remove_idents; use crate::can::pattern::PatternType::*; use crate::can::pattern::{canonicalize_pattern, idents_from_patterns, Pattern}; @@ -26,9 +27,11 @@ use std::fmt::Debug; #[derive(Clone, Debug, PartialEq)] pub struct Def { - pub pattern: Located, - pub expr: Located, - pub vars_by_symbol: SendMap, + pub loc_pattern: Located, + pub loc_expr: Located, + pub body_var: Variable, + pub pattern_vars: SendMap, + pub annotation: Option<(Type, SendMap)>, } #[derive(Debug)] @@ -41,6 +44,7 @@ pub struct CanDefs { #[inline(always)] pub fn canonicalize_defs<'a>( env: &mut Env, + found_rigids: &mut SendMap, var_store: &VarStore, scope: &mut Scope, loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located>>, @@ -77,6 +81,7 @@ pub fn canonicalize_defs<'a>( canonicalize_def( env, + found_rigids, Located { region: loc_def.region, value: &typed, @@ -90,6 +95,7 @@ pub fn canonicalize_defs<'a>( _ => { canonicalize_def( env, + found_rigids, Located { region: loc_def.region, value: &loc_def.value, @@ -106,6 +112,7 @@ pub fn canonicalize_defs<'a>( _ => { canonicalize_def( env, + found_rigids, Located { region: loc_def.region, value: &loc_def.value, @@ -229,10 +236,12 @@ pub fn sort_can_defs( let mut new_def = can_def.clone(); // Determine recursivity of closures that are not tail-recursive - if let Closure(name, Recursive::NotRecursive, args, body) = new_def.expr.value { + if let Closure(name, Recursive::NotRecursive, args, body) = + new_def.loc_expr.value + { let recursion = closure_recursivity(symbol.clone(), &env.closures); - new_def.expr.value = Closure(name, recursion, args, body); + new_def.loc_expr.value = Closure(name, recursion, args, body); } can_defs.push(new_def); @@ -272,7 +281,7 @@ pub fn sort_can_defs( let mut regions = Vec::with_capacity(can_defs_by_symbol.len()); for def in can_defs_by_symbol.values() { - regions.push((def.pattern.region, def.expr.region)); + regions.push((def.loc_pattern.region, def.loc_expr.region)); } ( @@ -288,7 +297,6 @@ fn canonicalize_def_pattern( loc_pattern: &Located, scope: &mut Scope, var_store: &VarStore, - expr_type: Type, ) -> Located { // Exclude the current ident from shadowable_idents; you can't shadow yourself! // (However, still include it in scope, because you *can* recursively refer to yourself.) @@ -308,6 +316,7 @@ fn canonicalize_def_pattern( fn canonicalize_def<'a>( env: &mut Env, + found_rigids: &mut SendMap, loc_def: Located<&'a ast::Def<'a>>, scope: &mut Scope, can_defs_by_symbol: &mut MutMap, @@ -318,21 +327,22 @@ fn canonicalize_def<'a>( // Make types for the body expr, even if we won't end up having a body. let expr_var = var_store.fresh(); - let expr_type = Type::Variable(expr_var); let mut vars_by_symbol = SendMap::default(); // Each def gets to have all the idents in scope that are defined in this // block. Order of defs doesn't matter, thanks to referential transparency! match loc_def.value { Annotation(loc_pattern, loc_annotation) => { - let loc_can_pattern = - canonicalize_def_pattern(env, loc_pattern, scope, var_store, expr_type.clone()); + let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store); // annotation sans body cannot introduce new rigids that are visible in other annotations // but the rigids can show up in type error messages, so still register them - let (_, can_annotation) = canonicalize_annotation(&loc_annotation.value, var_store); - if true { - panic!("TODO replace this call to canonicalize_annotation with something that *only* gets the arity, since that's all we use"); + let (seen_rigids, can_annotation) = + canonicalize_annotation(&loc_annotation.value, var_store); + + // union seen rigids with already found ones + for (k, v) in seen_rigids { + found_rigids.insert(k, v); } let arity = can_annotation.arity(); @@ -364,7 +374,7 @@ fn canonicalize_def<'a>( region: loc_annotation.region, }; - let body = Box::new((var_store.fresh(), body_expr)); + let body = Box::new((body_expr, var_store.fresh())); Located { value: Closure(symbol, Recursive::NotRecursive, underscores, body), @@ -376,22 +386,31 @@ fn canonicalize_def<'a>( can_defs_by_symbol.insert( symbol, Def { + body_var: var_store.fresh(), // TODO try to remove this .clone()! - pattern: loc_can_pattern.clone(), - expr: Located { + loc_pattern: loc_can_pattern.clone(), + loc_expr: Located { region: loc_can_expr.region, // TODO try to remove this .clone()! value: loc_can_expr.value.clone(), }, - vars_by_symbol: im::HashMap::clone(&vars_by_symbol), + pattern_vars: im::HashMap::clone(&vars_by_symbol), + annotation: Some((can_annotation.clone(), found_rigids.clone())), }, ); } } TypedDef(loc_pattern, loc_annotation, loc_expr) => { - let loc_can_pattern = - canonicalize_def_pattern(env, loc_pattern, scope, var_store, expr_type.clone()); + let (seen_rigids, can_annotation) = + canonicalize_annotation(&loc_annotation.value, var_store); + + // union seen rigids with already found ones + for (k, v) in seen_rigids { + found_rigids.insert(k, v); + } + + let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store); // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. @@ -468,8 +487,6 @@ fn canonicalize_def<'a>( ); } - let mut defined_symbols = Vec::new(); - // Store the referenced locals in the refs_by_symbol map, so we can later figure out // which defined names reference each other. for (ident, (symbol, region)) in @@ -496,21 +513,19 @@ fn canonicalize_def<'a>( ), ); - defined_symbols.push(symbol.clone()); - } - - for symbol in defined_symbols { can_defs_by_symbol.insert( symbol, Def { + body_var: var_store.fresh(), // TODO try to remove this .clone()! - pattern: loc_can_pattern.clone(), - expr: Located { + loc_pattern: loc_can_pattern.clone(), + loc_expr: Located { region: loc_can_expr.region, // TODO try to remove this .clone()! value: loc_can_expr.value.clone(), }, - vars_by_symbol: im::HashMap::clone(&vars_by_symbol), + pattern_vars: im::HashMap::clone(&vars_by_symbol), + annotation: Some((can_annotation.clone(), found_rigids.clone())), }, ); } @@ -518,8 +533,7 @@ fn canonicalize_def<'a>( // If we have a pattern, then the def has a body (that is, it's not a // standalone annotation), so we need to canonicalize the pattern and expr. Body(loc_pattern, loc_expr) => { - let loc_can_pattern = - canonicalize_def_pattern(env, loc_pattern, scope, var_store, expr_type.clone()); + let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store); // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. @@ -600,8 +614,6 @@ fn canonicalize_def<'a>( ); } - let mut defined_symbols = Vec::new(); - // Store the referenced locals in the refs_by_symbol map, so we can later figure out // which defined names reference each other. for (ident, (symbol, region)) in @@ -628,21 +640,19 @@ fn canonicalize_def<'a>( ), ); - defined_symbols.push(symbol.clone()); - } - - for symbol in defined_symbols { can_defs_by_symbol.insert( symbol, Def { + body_var: var_store.fresh(), // TODO try to remove this .clone()! - pattern: loc_can_pattern.clone(), - expr: Located { + loc_pattern: loc_can_pattern.clone(), + loc_expr: Located { region: loc_can_expr.region, // TODO try to remove this .clone()! value: loc_can_expr.value.clone(), }, - vars_by_symbol: im::HashMap::clone(&vars_by_symbol), + pattern_vars: im::HashMap::clone(&vars_by_symbol), + annotation: None, }, ); } @@ -651,6 +661,7 @@ fn canonicalize_def<'a>( Nested(value) => { canonicalize_def( env, + found_rigids, Located { value, region: loc_def.region, @@ -723,7 +734,8 @@ pub fn can_defs_with_return<'a>( loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located>>, loc_ret: &'a Located>, ) -> (Expr, Output) { - let unsorted = canonicalize_defs(env, var_store, &mut scope, loc_defs); + let mut found_rigids = SendMap::default(); + let unsorted = canonicalize_defs(env, &mut found_rigids, var_store, &mut scope, loc_defs); // The def as a whole is a tail call iff its return expression is a tail call. // Use its output as a starting point because its tail_call already has the right answer! @@ -732,6 +744,8 @@ pub fn can_defs_with_return<'a>( let (can_defs, mut output) = sort_can_defs(env, unsorted, output); + output.rigids = output.rigids.union(found_rigids); + match can_defs { Ok(defs) => (Defs(defs, Box::new(ret_expr)), output), Err(err) => (RuntimeError(err), output), diff --git a/src/can/expr.rs b/src/can/expr.rs index 052466274a..d5e4e2839e 100644 --- a/src/can/expr.rs +++ b/src/can/expr.rs @@ -5,9 +5,9 @@ use crate::can::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, int_expr_from_result, }; +use crate::can::pattern::idents_from_patterns; use crate::can::pattern::PatternType::*; use crate::can::pattern::{canonicalize_pattern, remove_idents, Pattern}; -use crate::can::pattern::{idents_from_patterns, PatternState}; use crate::can::problem::Problem; use crate::can::problem::RuntimeError; use crate::can::problem::RuntimeError::*; @@ -20,23 +20,16 @@ use crate::operator::CalledVia; use crate::parse::ast; use crate::region::{Located, Region}; use crate::subs::{VarStore, Variable}; -use crate::types::AnnotationSource::*; -use crate::types::Expected::{self, *}; -use crate::types::Type::{self, *}; use im_rc::Vector; use std::fmt::Debug; use std::i64; use std::ops::Neg; -/// Whenever we encounter a user-defined type variable (a "rigid" var for short), -/// for example `a` in the annotation `identity : a -> a`, we add it to this -/// map so that expressions within that annotation can share these vars. -pub type Rigids = ImMap, Type>; - #[derive(Clone, Default, Debug, PartialEq)] pub struct Output { pub references: References, pub tail_call: Option, + pub rigids: SendMap, } #[derive(Clone, Debug, PartialEq)] @@ -49,31 +42,35 @@ pub enum Expr { List(Variable, Vec<(Variable, Located)>), // Lookups - Var(Variable, Symbol), - /// Works the same as Var, but has an important marking purpose. - /// See 13623e3f5f65ea2d703cf155f16650c1e8246502 for the bug this fixed. - FunctionPointer(Variable, Symbol), - + Var { + symbol_for_lookup: Symbol, + resolved_symbol: Symbol, + }, // Pattern Matching /// When is guaranteed to be exhaustive at this point. (If it wasn't, then /// a _ branch was added at the end that will throw a runtime error.) /// Also, `If` is desugared into `When` matching on `False` and `_` at this point. - When( - Variable, - Box>, - Vec<((Variable, Located), (Variable, Located))>, - ), + When { + cond_var: Variable, + expr_var: Variable, + loc_cond: Box>, + branches: Vec<(Located, Located)>, + }, Defs(Vec, Box>), /// This is *only* for calling functions, not for tag application. /// The Tag variant contains any applied values inside it. - Call(Box, Vec<(Variable, Located)>, CalledVia), + Call( + Box<(Variable, Located, Variable)>, + Vec<(Variable, Located)>, + CalledVia, + ), Closure( Symbol, Recursive, Vec<(Variable, Located)>, - Box<(Variable, Located)>, + Box<(Located, Variable)>, ), // Product Types @@ -190,11 +187,10 @@ pub fn canonicalize_expr( // The expression that evaluates to the function being called, e.g. `foo` in // (foo) bar baz let fn_region = loc_fn.region; - // TODO look up the name and use NamedFnArg if possible. // Canonicalize the function expression and its arguments let (fn_expr, mut output) = - canonicalize_expr(env, var_store, scope, loc_fn.region, &loc_fn.value); + canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value); // The function's return type let mut args = Vec::new(); @@ -212,26 +208,35 @@ pub fn canonicalize_expr( output.tail_call = None; let expr = match fn_expr.value { - Var(_, ref sym) | FunctionPointer(_, ref sym) => { - // In the FunctionPointer case, we're calling an inline closure; - // something like ((\a b -> a + b) 1 2). - output.references.calls.insert(sym.clone()); + Var { + ref resolved_symbol, + .. + } => { + output.references.calls.insert(resolved_symbol.clone()); // we're tail-calling a symbol by name, check if it's the tail-callable symbol output.tail_call = match &env.tailcallable_symbol { - Some(tc_sym) if tc_sym == sym => Some(sym.clone()), + Some(tc_sym) if tc_sym == resolved_symbol => Some(resolved_symbol.clone()), Some(_) | None => None, }; - Call(Box::new(fn_expr.value), args, *application_style) + Call( + Box::new((var_store.fresh(), fn_expr, var_store.fresh())), + args, + *application_style, + ) } RuntimeError(_) => { // We can't call a runtime error; bail out by propagating it! return (fn_expr, output); } - not_var => { + _ => { // This could be something like ((if True then fn1 else fn2) arg1 arg2). - Call(Box::new(not_var), args, *application_style) + Call( + Box::new((var_store.fresh(), fn_expr, var_store.fresh())), + args, + *application_style, + ) } }; @@ -250,7 +255,7 @@ pub fn canonicalize_expr( let ident = Ident::new(module_parts, name); - canonicalize_lookup(env, scope, ident, symbol, region, var_store) + canonicalize_lookup(env, scope, ident, symbol, region) } //ast::Expr::InterpolatedStr(pairs, suffix) => { // let mut output = Output::new(); // let can_pairs: Vec<(String, Located)> = pairs @@ -326,11 +331,6 @@ pub fn canonicalize_expr( // it means there was shadowing, which will be handled later. scope.idents = union_pairs(scope.idents, arg_idents.iter()); - let mut state = PatternState { - headers: SendMap::default(), - vars: Vec::with_capacity(loc_arg_patterns.len()), - constraints: Vec::with_capacity(1), - }; let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); for loc_pattern in loc_arg_patterns.into_iter() { @@ -384,7 +384,7 @@ pub fn canonicalize_expr( symbol, Recursive::NotRecursive, can_args, - Box::new((var_store.fresh(), loc_body_expr)), + Box::new((loc_body_expr, var_store.fresh())), ), output, ) @@ -392,7 +392,6 @@ pub fn canonicalize_expr( ast::Expr::When(loc_cond, branches) => { // Infer the condition expression's type. let cond_var = var_store.fresh(); - let cond_type = Variable(cond_var); let (can_cond, mut output) = canonicalize_expr(env, var_store, scope, region, &loc_cond.value); @@ -401,7 +400,7 @@ pub fn canonicalize_expr( let mut can_branches = Vec::with_capacity(branches.len()); - for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() { + for (loc_pattern, loc_expr) in branches { let mut shadowable_idents = scope.idents.clone(); remove_idents(&loc_pattern.value, &mut shadowable_idents); @@ -418,10 +417,7 @@ pub fn canonicalize_expr( output.references = output.references.union(branch_references); - can_branches.push(( - (var_store.fresh(), can_pattern), - (var_store.fresh(), loc_can_expr), - )); + can_branches.push((can_pattern, loc_can_expr)); } // A "when" with no branches is a runtime error, but it will mess things up @@ -432,7 +428,12 @@ pub fn canonicalize_expr( } // Incorporate all three expressions into a combined Output value. - let expr = When(cond_var, Box::new(can_cond), can_branches); + let expr = When { + expr_var: var_store.fresh(), + cond_var, + loc_cond: Box::new(can_cond), + branches: can_branches, + }; (expr, output) } @@ -549,15 +550,17 @@ fn canonicalize_lookup( env: &mut Env, scope: &Scope, ident: Ident, - symbol: Symbol, + symbol_for_lookup: Symbol, region: Region, - var_store: &VarStore, ) -> (Expr, Output) { use self::Expr::*; let mut output = Output::default(); let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) { - Ok(sub_symbol) => Var(var_store.fresh(), sub_symbol), + Ok(resolved_symbol) => Var { + symbol_for_lookup, + resolved_symbol, + }, Err(ident) => { let loc_ident = Located { region, diff --git a/src/can/module.rs b/src/can/module.rs index 63a33d71f3..50092c960f 100644 --- a/src/can/module.rs +++ b/src/can/module.rs @@ -6,8 +6,9 @@ use crate::can::scope::Scope; use crate::can::symbol::Symbol; use crate::collections::SendMap; use crate::parse::ast::{self, ExposesEntry}; -use crate::region::Located; +use crate::region::{Located, Region}; use crate::subs::{VarStore, Variable}; +use crate::types::Constraint; use bumpalo::Bump; #[derive(Clone, Debug, PartialEq)] @@ -15,6 +16,7 @@ pub struct Module { pub name: Option>, pub defs: Vec, pub exposed_imports: SendMap, + pub constraint: Constraint, } pub fn canonicalize_module_defs<'a, I>( @@ -24,7 +26,11 @@ pub fn canonicalize_module_defs<'a, I>( _exposes: I, scope: &mut Scope, var_store: &VarStore, -) -> (Vec, SendMap) +) -> ( + Vec, + SendMap, + Vec<(Symbol, Variable, Region)>, +) where I: Iterator>>, { @@ -48,6 +54,7 @@ where } let mut env = Env::new(home); + let mut lookups = Vec::with_capacity(scope.idents.len()); // Exposed values are treated like defs that appear before any others, e.g. // @@ -62,17 +69,24 @@ where // by canonicalizing them right before we canonicalize the actual ast::Def nodes. for (ident, (symbol, region)) in scope.idents.iter() { if ident.first_char().is_lowercase() { + let expr_var = var_store.fresh(); + // Add an entry to exposed_imports using the current module's name // as the key; e.g. if this is the Foo module and we have // exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when // anything references `baz` in this Foo module, it will resolve to Bar.baz. - exposed_imports.insert(scope.symbol(&*ident.clone().name()), var_store.fresh()); + exposed_imports.insert(scope.symbol(&*ident.clone().name()), expr_var); + + // This will be used during constraint generation, + // to add the usual Lookup constraint as if this were a normal def. + lookups.push((symbol.clone(), expr_var, *region)); } else { // TODO add type aliases to type alias dictionary, based on exposed types } } - let defs = canonicalize_defs(&mut env, var_store, scope, &desugared); + let mut output = Output::default(); + let defs = canonicalize_defs(&mut env, &mut output.rigids, var_store, scope, &desugared); let defs = match sort_can_defs(&mut env, defs, Output::default()) { (Ok(defs), _) => { // TODO examine the patterns, extract toplevel identifiers from them, @@ -89,5 +103,5 @@ where // TODO incorporate rigids into here (possibly by making this be a Let instead // of an And) - (defs, exposed_imports) + (defs, exposed_imports, lookups) } diff --git a/src/can/pattern.rs b/src/can/pattern.rs index 833d7dbd9e..2d1da2981f 100644 --- a/src/can/pattern.rs +++ b/src/can/pattern.rs @@ -4,13 +4,12 @@ use crate::can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_ use crate::can::problem::Problem; use crate::can::scope::Scope; use crate::can::symbol::Symbol; -use crate::collections::{ImMap, SendMap}; +use crate::collections::ImMap; use crate::ident::Ident; use crate::parse::ast; use crate::region::{Located, Region}; use crate::subs::VarStore; use crate::subs::Variable; -use crate::types::{Constraint, PExpected, PatternCategory, Type}; use im_rc::Vector; /// A pattern, including possible problems (e.g. shadowing) so that @@ -23,8 +22,16 @@ pub enum Pattern { AppliedTag(Symbol, Vec>), IntLiteral(i64), FloatLiteral(f64), - ExactString(Box), - RecordDestructure(Vec<(Located, Option>)>), + StrLiteral(Box), + RecordDestructure( + Variable, + Vec<( + Variable, + Lowercase, + Symbol, + Option<(Variable, Located)>, + )>, + ), Underscore, // Runtime Exceptions @@ -137,7 +144,7 @@ pub fn canonicalize_pattern<'a>( &StrLiteral(_string) => match pattern_type { WhenBranch => { panic!("TODO check whether string pattern is malformed."); - // Pattern::ExactString((*string).into()) + // Pattern::StrLiteral((*string).into()) } ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => { unsupported_pattern(env, ptype, region) @@ -157,32 +164,49 @@ pub fn canonicalize_pattern<'a>( } &RecordDestructure(patterns) => { let mut fields = Vec::with_capacity(patterns.len()); + for loc_pattern in patterns { match loc_pattern.value { - Identifier(ref name) => { - let result = match canonicalize_pattern_identifier( - name, + Identifier(label) => { + let symbol = match canonicalize_pattern_identifier( + &label, env, scope, region, shadowable_idents, ) { - Ok(symbol) => Pattern::Identifier(symbol), - Err(loc_shadowed_ident) => Pattern::Shadowed(loc_shadowed_ident), + Ok(symbol) => symbol, + Err(loc_shadowed_ident) => { + // If any idents are shadowed, consider the entire + // destructure pattern shadowed! + let _loc_pattern = Located { + region, + value: Pattern::Shadowed(loc_shadowed_ident), + }; + panic!("TODO gather all the shadowing errors, not just the first one, and report them in Problems."); + } }; - fields.push((Located::at(region, result), None)); + fields.push((var_store.fresh(), Lowercase::from(label), symbol, None)); } - RecordField(ref name, loc_guard) => { - let result = match canonicalize_pattern_identifier( - name, + RecordField(label, loc_guard) => { + let symbol = match canonicalize_pattern_identifier( + &label, env, scope, region, shadowable_idents, ) { - Ok(symbol) => Pattern::Identifier(symbol), - Err(loc_shadowed_ident) => Pattern::Shadowed(loc_shadowed_ident), + Ok(symbol) => symbol, + Err(loc_shadowed_ident) => { + // If any idents are shadowed, consider the entire + // destructure pattern shadowed! + let _loc_pattern = Located { + region, + value: Pattern::Shadowed(loc_shadowed_ident), + }; + panic!("TODO gather all the shadowing errors, not just the first one, and report them in Problems."); + } }; let can_guard = canonicalize_pattern( @@ -195,12 +219,18 @@ pub fn canonicalize_pattern<'a>( shadowable_idents, ); - fields.push((Located::at(region, result), Some(can_guard))); + fields.push(( + var_store.fresh(), + Lowercase::from(label), + symbol, + Some((var_store.fresh(), can_guard)), + )); } _ => panic!("invalid pattern in record"), } } - Pattern::RecordDestructure(fields) + + Pattern::RecordDestructure(var_store.fresh(), fields) } &RecordField(_name, _loc_pattern) => { unreachable!("should be handled in RecordDestructure"); @@ -279,10 +309,8 @@ pub fn canonicalize_pattern_identifier<'a>( // tag application patterns, which can bring multiple // new idents into scope. For example, it's important that // we catch (Blah foo foo) -> … as being an example of shadowing. - scope - .idents - .insert(new_ident.clone(), symbol_and_region.clone()); - shadowable_idents.insert(new_ident, symbol_and_region); + shadowable_idents.insert(new_ident.clone(), symbol_and_region.clone()); + scope.idents.insert(new_ident, symbol_and_region); Ok(symbol) } @@ -299,131 +327,6 @@ fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region) Pattern::UnsupportedPattern(region) } -// CONSTRAIN - -pub struct PatternState { - pub headers: SendMap>, - pub vars: Vec, - pub constraints: Vec, -} - -fn add_constraints<'a>( - pattern: &'a ast::Pattern<'a>, - scope: &'a Scope, - region: Region, - expected: PExpected, - state: &'a mut PatternState, - var_store: &VarStore, -) { - use crate::parse::ast::Pattern::*; - - match pattern { - Underscore | Malformed(_) | QualifiedIdentifier(_) => { - // Neither the _ pattern nor malformed ones add any constraints. - } - Identifier(name) => { - state.headers.insert( - scope.symbol(name), - Located { - region, - value: expected.get_type(), - }, - ); - } - IntLiteral(_) | NonBase10Literal { .. } => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Int, - Type::int(), - expected, - )); - } - - FloatLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Float, - Type::float(), - expected, - )); - } - - StrLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Str, - Type::string(), - expected, - )); - } - - BlockStrLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Str, - Type::string(), - expected, - )); - } - - SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => { - add_constraints(pattern, scope, region, expected, state, var_store) - } - - RecordDestructure(patterns) => { - let ext_var = var_store.fresh(); - let ext_type = Type::Variable(ext_var); - - let mut field_types: SendMap = SendMap::default(); - for loc_pattern in patterns { - let pat_var = var_store.fresh(); - let pat_type = Type::Variable(pat_var); - let expected = PExpected::NoExpectation(pat_type.clone()); - - match loc_pattern.value { - Identifier(name) | RecordField(name, _) => { - let symbol = scope.symbol(name); - if !state.headers.contains_key(&symbol) { - state - .headers - .insert(symbol, Located::at(region, pat_type.clone())); - } - field_types.insert(name.into(), pat_type.clone()); - } - _ => panic!("invalid record pattern"), - } - - if let RecordField(_, guard) = loc_pattern.value { - add_constraints( - &guard.value, - scope, - guard.region, - expected, - state, - var_store, - ); - } - - state.vars.push(pat_var); - } - - let record_type = Type::Record(field_types, Box::new(ext_type)); - let record_con = - Constraint::Pattern(region, PatternCategory::Record, record_type, expected); - - state.constraints.push(record_con); - } - - RecordField(_, _) => { - // unreachable, this pattern is handled by already by RecordDestructure - } - - GlobalTag(_) | PrivateTag(_) | Apply(_, _) => { - panic!("TODO add_constraints for {:?}", pattern); - } - } -} - pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap) { use crate::parse::ast::Pattern::*; diff --git a/src/constrain/builtins.rs b/src/constrain/builtins.rs new file mode 100644 index 0000000000..2b9b288bb5 --- /dev/null +++ b/src/constrain/builtins.rs @@ -0,0 +1,72 @@ +use crate::region::Region; +use crate::subs::Variable; +use crate::types::Constraint::{self, *}; +use crate::types::Expected::{self, *}; +use crate::types::Type::{self, *}; +use crate::types::{self, Reason}; + +#[inline(always)] +pub fn int_literal(var: Variable, expected: Expected, region: Region) -> Constraint { + let typ = number_literal_type("Int", "Integer"); + let reason = Reason::IntLiteral; + + num_literal(var, typ, reason, expected, region) +} + +#[inline(always)] +pub fn float_literal(var: Variable, expected: Expected, region: Region) -> Constraint { + let typ = number_literal_type("Float", "FloatingPoint"); + let reason = Reason::FloatLiteral; + + num_literal(var, typ, reason, expected, region) +} + +#[inline(always)] +fn num_literal( + num_var: Variable, + literal_type: Type, + reason: Reason, + expected: Expected, + region: Region, +) -> Constraint { + let num_type = Variable(num_var); + let expected_literal = ForReason(reason, literal_type, region); + + And(vec![ + Eq(num_type.clone(), expected_literal, region), + Eq(num_type, expected, region), + ]) +} + +#[inline(always)] +fn number_literal_type(module_name: &str, type_name: &str) -> Type { + builtin_type( + types::MOD_NUM, + types::TYPE_NUM, + vec![builtin_type(module_name, type_name, Vec::new())], + ) +} + +#[inline(always)] +fn builtin_type(module_name: &str, type_name: &str, args: Vec) -> Type { + Type::Apply { + module_name: module_name.into(), + name: type_name.into(), + args, + } +} + +#[inline(always)] +pub fn empty_list_type(var: Variable) -> Type { + list_type(Type::Variable(var)) +} + +#[inline(always)] +pub fn list_type(typ: Type) -> Type { + builtin_type("List", "List", vec![typ]) +} + +#[inline(always)] +pub fn str_type() -> Type { + builtin_type("Str", "Str", Vec::new()) +} diff --git a/src/constrain/expr.rs b/src/constrain/expr.rs new file mode 100644 index 0000000000..1d325ec74c --- /dev/null +++ b/src/constrain/expr.rs @@ -0,0 +1,603 @@ +use crate::can::def::Def; +use crate::can::expr::Expr::{self, *}; +use crate::can::ident::Lowercase; +use crate::can::pattern::Pattern; +use crate::can::symbol::Symbol; +use crate::collections::{ImMap, SendMap}; +use crate::constrain::builtins::{ + empty_list_type, float_literal, int_literal, list_type, str_type, +}; +use crate::constrain::pattern::{constrain_pattern, PatternState}; +use crate::region::{Located, Region}; +use crate::subs::Variable; +use crate::types::AnnotationSource::*; +use crate::types::Constraint::{self, *}; +use crate::types::Expected::{self, *}; +use crate::types::PReason; +use crate::types::Type::{self, *}; +use crate::types::{LetConstraint, PExpected, Reason}; + +/// Whenever we encounter a user-defined type variable (a "rigid" var for short), +/// for example `a` in the annotation `identity : a -> a`, we add it to this +/// map so that expressions within that annotation can share these vars. +pub type Rigids = ImMap; + +/// This is for constraining Defs +#[derive(Default)] +pub struct Info { + pub vars: Vec, + pub constraints: Vec, + pub def_types: SendMap>, +} + +impl Info { + pub fn with_capacity(capacity: usize) -> Self { + Info { + vars: Vec::with_capacity(capacity), + constraints: Vec::with_capacity(capacity), + def_types: SendMap::default(), + } + } +} + +#[inline(always)] +pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { + Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars, + def_types: SendMap::default(), + defs_constraint: constraint, + ret_constraint: Constraint::True, + })) +} + +pub fn constrain_expr( + rigids: &Rigids, + region: Region, + expr: &Expr, + expected: Expected, +) -> Constraint { + match expr { + Int(var, _) => int_literal(*var, expected, region), + Float(var, _) => float_literal(*var, expected, region), + EmptyRecord => constrain_empty_record(region, expected), + Expr::Record(stored_var, fields) => { + if fields.is_empty() { + constrain_empty_record(region, expected) + } else { + let mut field_exprs = SendMap::default(); + let mut field_types = SendMap::default(); + let mut field_vars = Vec::with_capacity(fields.len()); + + // Constraints need capacity for each field + 1 for the record itself. + let mut constraints = Vec::with_capacity(1 + fields.len()); + + for (label, (field_var, loc_field_expr)) in fields { + let (field_type, field_con) = + constrain_field(rigids, *field_var, loc_field_expr); + + field_vars.push(*field_var); + field_exprs.insert(label.clone(), loc_field_expr); + field_types.insert(label.clone(), field_type); + + constraints.push(field_con); + } + + let record_type = Type::Record( + field_types, + // TODO can we avoid doing Box::new on every single one of these? + // For example, could we have a single lazy_static global Box they + // could all share? + Box::new(Type::EmptyRec), + ); + let record_con = Eq(record_type, expected.clone(), region); + constraints.push(record_con); + + // variable to store in the AST + let stored_con = Eq(Type::Variable(*stored_var), expected, region); + + field_vars.push(*stored_var); + constraints.push(stored_con); + + exists(field_vars, And(constraints)) + } + } + Str(_) | BlockStr(_) => Eq(str_type(), expected, region), + List(list_var, loc_elems) => { + if loc_elems.is_empty() { + Eq(empty_list_type(*list_var), expected, region) + } else { + let list_elem_type = Type::Variable(*list_var); + let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2)); + + for (elem_var, loc_elem) in loc_elems { + let elem_type = Variable(*elem_var); + let elem_expected = NoExpectation(elem_type.clone()); + let list_elem_constraint = Eq( + list_elem_type.clone(), + ForReason(Reason::ElemInList, elem_type, region), + region, + ); + let constraint = + constrain_expr(rigids, loc_elem.region, &loc_elem.value, elem_expected); + + constraints.push(list_elem_constraint); + constraints.push(constraint); + } + + constraints.push(Eq(list_type(list_elem_type), expected, region)); + + And(constraints) + } + } + Call(boxed, loc_args, _application_style) => { + let (fn_var, loc_fn, ret_var) = &**boxed; + // The expression that evaluates to the function being called, e.g. `foo` in + // (foo) bar baz + let fn_type = Variable(*fn_var); + let fn_region = loc_fn.region; + let fn_expected = NoExpectation(fn_type.clone()); + // TODO look up the name and use NamedFnArg if possible. + let fn_reason = Reason::AnonymousFnCall { + arity: loc_args.len() as u8, + }; + + let fn_con = constrain_expr(rigids, loc_fn.region, &loc_fn.value, fn_expected); + + // The function's return type + let ret_type = Variable(*ret_var); + + // This will be used in the occurs check + let mut vars = Vec::with_capacity(2 + loc_args.len()); + + vars.push(*fn_var); + vars.push(*ret_var); + + let mut arg_types = Vec::with_capacity(loc_args.len()); + let mut arg_cons = Vec::with_capacity(loc_args.len()); + + for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() { + let region = loc_arg.region; + let arg_type = Variable(*arg_var); + // TODO look up the name and use NamedFnArg if possible. + let reason = Reason::AnonymousFnArg { + arg_index: index as u8, + }; + let expected_arg = ForReason(reason, arg_type.clone(), region); + let arg_con = constrain_expr(rigids, loc_arg.region, &loc_arg.value, expected_arg); + + vars.push(*arg_var); + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let expected_fn_type = ForReason( + fn_reason, + Function(arg_types, Box::new(ret_type.clone())), + region, + ); + + exists( + vars, + And(vec![ + fn_con, + Eq(fn_type, expected_fn_type, fn_region), + And(arg_cons), + Eq(ret_type, expected, region), + ]), + ) + } + Var { + symbol_for_lookup, .. + } => Lookup(symbol_for_lookup.clone(), expected, region), + Closure(_symbol, _recursive, args, boxed) => { + let (loc_body_expr, ret_var) = &**boxed; + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(args.len()), + constraints: Vec::with_capacity(1), + }; + let mut vars = Vec::with_capacity(state.vars.capacity() + 1); + let mut pattern_types = Vec::with_capacity(state.vars.capacity()); + let ret_var = *ret_var; + let ret_type = Type::Variable(ret_var); + + vars.push(ret_var); + + for (pattern_var, loc_pattern) in args { + let pattern_type = Type::Variable(*pattern_var); + let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); + + pattern_types.push(pattern_type); + + constrain_pattern( + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut state, + ); + + vars.push(*pattern_var); + } + + let fn_typ = Type::Function(pattern_types, Box::new(ret_type.clone())); + let body_type = NoExpectation(ret_type); + let ret_constraint = constrain_expr( + rigids, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); + + let defs_constraint = And(state.constraints); + + exists( + vars, + And(vec![ + Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint, + ret_constraint, + })), + // "the closure's type is equal to expected type" + Eq(fn_typ, expected, region), + ]), + ) + } + When { + cond_var, + expr_var, + loc_cond, + branches, + } => { + // Infer the condition expression's type. + let cond_var = *cond_var; + let cond_type = Variable(cond_var); + let expr_con = constrain_expr( + rigids, + region, + &loc_cond.value, + NoExpectation(cond_type.clone()), + ); + + let mut constraints = Vec::with_capacity(branches.len() + 1); + + match expected { + FromAnnotation(name, arity, _, typ) => { + for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() { + let branch_con = constrain_when_branch( + rigids, + region, + loc_pattern, + loc_expr, + PExpected::ForReason( + PReason::WhenMatch { index }, + cond_type.clone(), + region, + ), + FromAnnotation( + name.clone(), + arity, + TypedWhenBranch(index), + typ.clone(), + ), + ); + + // TODO investigate: why doesn't this use expr_var? + // Shouldn't it? + constraints.push(exists( + vec![cond_var], + // Each branch's pattern must have the same type + // as the condition expression did. + And(vec![expr_con.clone(), branch_con]), + )); + } + } + + _ => { + let branch_type = Variable(*expr_var); + let mut branch_cons = Vec::with_capacity(branches.len()); + + for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() { + let branch_con = constrain_when_branch( + rigids, + region, + loc_pattern, + loc_expr, + PExpected::ForReason( + PReason::WhenMatch { index }, + cond_type.clone(), + region, + ), + ForReason(Reason::WhenBranch { index }, branch_type.clone(), region), + ); + + branch_cons.push(branch_con); + } + + constraints.push(exists( + vec![cond_var], + And(vec![ + // Record the original conditional expression's constraint. + expr_con, + // Each branch's pattern must have the same type + // as the condition expression did. + And(branch_cons), + // The return type of each branch must equal + // the return type of the entire when-expression. + Eq(branch_type, expected, region), + ]), + )); + } + } + + // TODO check for exhaustiveness. If this `case` is non-exaustive, then: + // + // 1. Record a Problem. + // 2. Add an extra _ branch at the end which throws a runtime error. + + And(constraints) + } + Access { + ext_var, + field_var, + loc_expr, + field, + } => { + let ext_var = *ext_var; + let ext_type = Type::Variable(ext_var); + let field_var = *field_var; + let field_type = Type::Variable(field_var); + + let mut rec_field_types = SendMap::default(); + + rec_field_types.insert(field.clone(), field_type.clone()); + + let record_type = Type::Record(rec_field_types, Box::new(ext_type)); + let record_expected = Expected::NoExpectation(record_type); + + let constraint = + constrain_expr(&ImMap::default(), region, &loc_expr.value, record_expected); + + exists( + vec![field_var, ext_var], + And(vec![constraint, Eq(field_type, expected, region)]), + ) + } + Accessor { + field, + ext_var, + field_var, + } => { + let ext_var = *ext_var; + let ext_type = Variable(ext_var); + let field_var = *field_var; + let field_type = Variable(field_var); + + let mut field_types = SendMap::default(); + field_types.insert(field.clone(), field_type.clone()); + let record_type = Type::Record(field_types, Box::new(ext_type)); + + exists( + vec![field_var, ext_var], + Eq( + Type::Function(vec![record_type], Box::new(field_type)), + expected, + region, + ), + ) + } + Defs(defs, loc_ret) => constrain_defs_with_return( + rigids, + defs, + expected, + Info::with_capacity(defs.len()), + Info::with_capacity(defs.len()), + loc_ret, + ), + Tag(_, _) => { + panic!("TODO constrain Tag"); + } + RuntimeError(_) => True, + } +} + +#[inline(always)] +fn constrain_when_branch<'a>( + rigids: &Rigids, + region: Region, + loc_pattern: &Located, + loc_expr: &Located, + pattern_expected: PExpected, + expr_expected: Expected, +) -> Constraint { + let ret_constraint = constrain_expr(rigids, region, &loc_expr.value, expr_expected); + + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(1), + constraints: Vec::with_capacity(1), + }; + + constrain_pattern( + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut state, + ); + + Constraint::Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Constraint::And(state.constraints), + ret_constraint, + })) +} + +fn constrain_field( + rigids: &Rigids, + field_var: Variable, + loc_expr: &Located, +) -> (Type, Constraint) { + let field_type = Variable(field_var); + let field_expected = NoExpectation(field_type.clone()); + let constraint = constrain_expr(rigids, loc_expr.region, &loc_expr.value, field_expected); + + (field_type, constraint) +} + +#[inline(always)] +fn constrain_empty_record(region: Region, expected: Expected) -> Constraint { + Eq(EmptyRec, expected, region) +} + +#[inline(always)] +pub fn constrain_defs( + rigids: &Rigids, + found_rigids: &mut SendMap, + defs: &[Def], + flex_info: &mut Info, +) { + for def in defs { + constrain_def(rigids, found_rigids, def, flex_info) + } +} + +fn constrain_def_pattern(loc_pattern: &Located, expr_type: Type) -> PatternState { + // Exclude the current ident from shadowable_idents; you can't shadow yourself! + // (However, still include it in scope, because you *can* recursively refer to yourself.) + let pattern_expected = PExpected::NoExpectation(expr_type); + + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(1), + constraints: Vec::with_capacity(1), + }; + + constrain_pattern( + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut state, + ); + + state +} + +fn constrain_def( + rigids: &Rigids, + found_rigids: &mut SendMap, + def: &Def, + flex_info: &mut Info, +) { + use crate::types::AnnotationSource; + + let expr_var = def.body_var; + let expr_type = Type::Variable(expr_var); + + flex_info.vars.push(expr_var); + + let pattern_state = constrain_def_pattern(&def.loc_pattern, expr_type.clone()); + + for (k, v) in &pattern_state.headers { + flex_info.def_types.insert(k.clone(), v.clone()); + } + + let ret_constraint = match &def.annotation { + Some((annotation, seen_rigids)) => { + let mut ftv: Rigids = rigids.clone(); + + for (var, name) in seen_rigids { + // if the rigid is known already, nothing needs to happen + // otherwise register it. + if !rigids.contains_key(name) { + // possible use this rigid in nested def's + ftv.insert(name.clone(), Type::Variable(*var)); + + // mark this variable as a rigid + found_rigids.insert(*var, name.clone()); + } + } + + let annotation_expected = FromAnnotation( + def.loc_pattern.clone(), + annotation.arity(), + AnnotationSource::TypedBody, + annotation.clone(), + ); + + // ensure expected type unifies with annotated type + flex_info.constraints.push(Eq( + expr_type, + annotation_expected.clone(), + def.loc_expr.region, + )); + + constrain_expr( + &ftv, + def.loc_expr.region, + &def.loc_expr.value, + annotation_expected, + ) + } + None => constrain_expr( + rigids, + def.loc_expr.region, + &def.loc_expr.value, + NoExpectation(expr_type), + ), + }; + + flex_info.constraints.push(Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: pattern_state.vars, + def_types: pattern_state.headers, + defs_constraint: And(pattern_state.constraints), + ret_constraint, + }))); +} + +#[inline(always)] +pub fn constrain_defs_with_return<'a>( + rigids: &Rigids, + defs: &[Def], + expected: Expected, + mut flex_info: Info, + rigid_info: Info, + loc_ret: &'a Located, +) -> Constraint { + let mut found_rigids = SendMap::default(); + + constrain_defs(rigids, &mut found_rigids, defs, &mut flex_info); + + // The def as a whole is a tail call iff its return expression is a tail call. + // Use its output as a starting point because its tail_call already has the right answer! + let ret_con = constrain_expr(rigids, loc_ret.region, &loc_ret.value, expected); + + // Rigid constraint for the def expr as a whole. + // This is a "LetRec" constraint; it supports recursion. + // (The only advantage of "Let" over "LetRec" is if you want to + // shadow things, and Roc disallows shadowing anyway.) + Let(Box::new(LetConstraint { + rigid_vars: rigid_info.vars, + flex_vars: Vec::new(), + def_types: rigid_info.def_types, + defs_constraint: True, + ret_constraint: Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: flex_info.vars, + def_types: flex_info.def_types.clone(), + defs_constraint: Let(Box::new(LetConstraint { + flex_vars: Vec::new(), + rigid_vars: Vec::new(), + def_types: flex_info.def_types, + defs_constraint: True, + ret_constraint: And(flex_info.constraints), + })), + ret_constraint: And(vec![And(rigid_info.constraints), ret_con]), + })), + })) +} diff --git a/src/constrain/mod.rs b/src/constrain/mod.rs index 171d4fcf88..37f49a1823 100644 --- a/src/constrain/mod.rs +++ b/src/constrain/mod.rs @@ -1,85 +1,4 @@ -use crate::collections::SendMap; -use crate::region::Region; -use crate::subs::{VarStore, Variable}; -use crate::types::Constraint::{self, *}; -use crate::types::Expected::{self, *}; -use crate::types::Type::{self, *}; -use crate::types::{self, LetConstraint, Reason}; - -#[inline(always)] -pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars, - def_types: SendMap::default(), - defs_constraint: constraint, - ret_constraint: Constraint::True, - })) -} - -#[inline(always)] -pub fn int_literal(var_store: &VarStore, expected: Expected, region: Region) -> Constraint { - let typ = number_literal_type("Int", "Integer"); - let reason = Reason::IntLiteral; - - num_literal(var_store, typ, reason, expected, region) -} - -#[inline(always)] -pub fn float_literal(var_store: &VarStore, expected: Expected, region: Region) -> Constraint { - let typ = number_literal_type("Float", "FloatingPoint"); - let reason = Reason::FloatLiteral; - - num_literal(var_store, typ, reason, expected, region) -} - -#[inline(always)] -fn num_literal( - var_store: &VarStore, - literal_type: Type, - reason: Reason, - expected: Expected, - region: Region, -) -> Constraint { - let num_var = var_store.fresh(); - let num_type = Variable(num_var); - let expected_literal = ForReason(reason, literal_type, region); - - And(vec![ - Eq(num_type.clone(), expected_literal, region), - Eq(num_type, expected, region), - ]) -} - -#[inline(always)] -fn number_literal_type(module_name: &str, type_name: &str) -> Type { - builtin_type( - types::MOD_NUM, - types::TYPE_NUM, - vec![builtin_type(module_name, type_name, Vec::new())], - ) -} - -#[inline(always)] -fn builtin_type(module_name: &str, type_name: &str, args: Vec) -> Type { - Type::Apply { - module_name: module_name.into(), - name: type_name.into(), - args, - } -} - -#[inline(always)] -pub fn empty_list_type(var: Variable) -> Type { - list_type(Type::Variable(var)) -} - -#[inline(always)] -pub fn list_type(typ: Type) -> Type { - builtin_type("List", "List", vec![typ]) -} - -#[inline(always)] -pub fn str_type() -> Type { - builtin_type("Str", "Str", Vec::new()) -} +pub mod builtins; +pub mod expr; +pub mod module; +pub mod pattern; diff --git a/src/constrain/module.rs b/src/constrain/module.rs new file mode 100644 index 0000000000..9e8ba66e84 --- /dev/null +++ b/src/constrain/module.rs @@ -0,0 +1,31 @@ +use crate::can::def::Def; +use crate::can::symbol::Symbol; +use crate::collections::{ImMap, SendMap}; +use crate::constrain::expr::{constrain_defs, Info}; +use crate::region::Region; +use crate::subs::Variable; +use crate::types::Constraint::{self, *}; +use crate::types::Expected::*; +use crate::types::Type; + +#[inline(always)] +pub fn constrain_module(defs: &[Def], lookups: Vec<(Symbol, Variable, Region)>) -> Constraint { + let mut flex_info = Info::default(); + + for (symbol, expr_var, region) in lookups { + // Add the usual Lookup constraint as if this were a normal def. + let expr_type = Type::Variable(expr_var); + let expected = NoExpectation(expr_type.clone()); + + flex_info.constraints.push(Lookup(symbol, expected, region)); + } + + constrain_defs( + &ImMap::default(), + &mut SendMap::default(), + &defs, + &mut flex_info, + ); + + Constraint::And(flex_info.constraints) +} diff --git a/src/constrain/pattern.rs b/src/constrain/pattern.rs new file mode 100644 index 0000000000..dcbc088f27 --- /dev/null +++ b/src/constrain/pattern.rs @@ -0,0 +1,106 @@ +use crate::can::ident::Lowercase; +use crate::can::pattern::Pattern::{self, *}; +use crate::can::symbol::Symbol; +use crate::collections::SendMap; +use crate::region::{Located, Region}; +use crate::subs::Variable; +use crate::types::{Constraint, PExpected, PatternCategory, Type}; + +pub struct PatternState { + pub headers: SendMap>, + pub vars: Vec, + pub constraints: Vec, +} + +/// This accepts PatternState (rather than returning it) so that the caller can +/// intiialize the Vecs in PatternState using with_capacity +/// based on its knowledge of their lengths. +pub fn constrain_pattern( + pattern: &Pattern, + region: Region, + expected: PExpected, + state: &mut PatternState, +) { + match pattern { + Underscore | UnsupportedPattern(_) => { + // Neither the _ pattern nor erroneous ones add any constraints. + } + Identifier(symbol) => { + state.headers.insert( + symbol.clone(), + Located { + region, + value: expected.get_type(), + }, + ); + } + IntLiteral(_) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Int, + Type::int(), + expected, + )); + } + + FloatLiteral(_) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Float, + Type::float(), + expected, + )); + } + + StrLiteral(_) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Str, + Type::string(), + expected, + )); + } + + RecordDestructure(ext_var, patterns) => { + let ext_type = Type::Variable(*ext_var); + + let mut field_types: SendMap = SendMap::default(); + + for (pat_var, label, symbol, opt_guard) in patterns { + let pat_type = Type::Variable(*pat_var); + let expected = PExpected::NoExpectation(pat_type.clone()); + + if !state.headers.contains_key(&symbol) { + state + .headers + .insert(symbol.clone(), Located::at(region, pat_type.clone())); + } + + field_types.insert(label.clone(), pat_type.clone()); + + // TODO investigate: shouldn't guard_var be constrained somewhere? + if let Some((_guard_var, loc_guard)) = opt_guard { + constrain_pattern(&loc_guard.value, loc_guard.region, expected, state); + } + + state.vars.push(*pat_var); + } + + let record_type = Type::Record(field_types, Box::new(ext_type)); + let record_con = + Constraint::Pattern(region, PatternCategory::Record, record_type, expected); + + state.constraints.push(record_con); + } + + Tag(_) => { + panic!("TODO constrain Tag pattern"); + } + AppliedTag(_, _) => { + panic!("TODO constrain AppliedTag pattern"); + } + Shadowed(_) => { + panic!("TODO constrain Shadowed pattern"); + } + } +} diff --git a/src/gen/mod.rs b/src/gen/mod.rs index b86a76eab3..752a378789 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -118,20 +118,24 @@ fn compile_expr<'ctx, 'env>( match *expr { Int(_, num) => IntConst(env.context.i64_type().const_int(num as u64, false)), Float(_, num) => FloatConst(env.context.f64_type().const_float(num)), - When(_, ref loc_cond_expr, ref branches) => { + When { + ref loc_cond, + ref branches, + .. + } => { if branches.len() < 2 { panic!("TODO support when-expressions of fewer than 2 branches."); } if branches.len() == 2 { let mut iter = branches.iter(); - let ((_pattern_var, pattern), (_expr_var, branch_expr)) = iter.next().unwrap(); - let (_, (_, else_expr)) = iter.next().unwrap(); + let (pattern, branch_expr) = iter.next().unwrap(); + let (_, else_expr) = iter.next().unwrap(); compile_when_branch( env, parent, - &loc_cond_expr.value, + &loc_cond.value, pattern.value.clone(), &branch_expr.value, &else_expr.value, diff --git a/src/load/mod.rs b/src/load/mod.rs index 8b59e58b58..2f1ce8cfda 100644 --- a/src/load/mod.rs +++ b/src/load/mod.rs @@ -3,6 +3,7 @@ use crate::can::module::{canonicalize_module_defs, Module}; use crate::can::scope::Scope; use crate::can::symbol::Symbol; use crate::collections::{ImMap, SendMap, SendSet}; +use crate::constrain::module::constrain_module; use crate::ident::Ident; use crate::module::ModuleName; use crate::parse::ast::{self, Attempting, ExposesEntry, ImportsEntry}; @@ -12,6 +13,7 @@ use crate::region::{Located, Region}; use crate::solve; use crate::subs::VarStore; use crate::subs::{Subs, Variable}; +use crate::types::Constraint; use crate::unify::Problems; use bumpalo::Bump; use futures::future::join_all; @@ -217,8 +219,7 @@ fn load_filename( let mut scope = Scope::new(format!("{}.", declared_name).into(), scope_from_imports); - - let (defs, exposed_imports) = parse_and_canonicalize_defs( + let (defs, exposed_imports, constraint) = process_defs( &arena, state, declared_name.clone(), @@ -230,6 +231,7 @@ fn load_filename( name: Some(declared_name), defs, exposed_imports, + constraint, }; LoadedModule::Valid(module) @@ -258,7 +260,7 @@ fn load_filename( let mut scope = Scope::new(".".into(), scope_from_imports); // The app module has no declared name. Pass it as "". - let (defs, exposed_imports) = parse_and_canonicalize_defs( + let (defs, exposed_imports, constraint) = process_defs( &arena, state, "".into(), @@ -270,6 +272,7 @@ fn load_filename( name: None, defs, exposed_imports, + constraint, }; LoadedModule::Valid(module) @@ -286,14 +289,14 @@ fn load_filename( } } -fn parse_and_canonicalize_defs<'a, I>( +fn process_defs<'a, I>( arena: &'a Bump, state: State<'a>, home: Box, exposes: I, scope: &mut Scope, var_store: &VarStore, -) -> (Vec, SendMap) +) -> (Vec, SendMap, Constraint) where I: Iterator>>, { @@ -301,7 +304,12 @@ where .parse(arena, state) .expect("TODO gracefully handle parse error on module defs"); - canonicalize_module_defs(arena, parsed_defs, home, exposes, scope, var_store) + let (defs, exposed_imports, lookups) = + canonicalize_module_defs(arena, parsed_defs, home, exposes, scope, var_store); + + let constraint = constrain_module(&defs, lookups); + + (defs, exposed_imports, constraint) } fn load_import( @@ -360,21 +368,16 @@ pub fn solve_loaded( use LoadedModule::*; let mut vars_by_symbol: ImMap = ImMap::default(); - let mut constraints = Vec::with_capacity(loaded_deps.len() + 1); - let module_constraint = if true { - panic!("TODO populate constraints for each module"); - } else { - crate::types::Constraint::True - }; + let mut dep_constraints = Vec::with_capacity(loaded_deps.len()); // All the exposed imports should be available in the solver's vars_by_symbol - for (symbol, var) in module.exposed_imports.iter() { - vars_by_symbol.insert(symbol.clone(), var.clone()); + for (symbol, expr_var) in module.exposed_imports.iter() { + vars_by_symbol.insert(symbol.clone(), expr_var.clone()); } // All the top-level defs should also be available in vars_by_symbol for def in module.defs.iter() { - for (symbol, var) in def.vars_by_symbol.iter() { + for (symbol, var) in def.pattern_vars.iter() { vars_by_symbol.insert(symbol.clone(), var.clone()); } } @@ -391,16 +394,18 @@ pub fn solve_loaded( // in the solver's vars_by_symbol. (The map's keys are // fully qualified, so there won't be any collisions // with the primary module's exposed imports!) - for (symbol, var) in valid_dep.exposed_imports { - vars_by_symbol.insert(symbol, var); + for (symbol, expr_var) in valid_dep.exposed_imports { + vars_by_symbol.insert(symbol, expr_var); } // All its top-level defs should also be available in vars_by_symbol for def in valid_dep.defs { - for (symbol, var) in def.vars_by_symbol { + for (symbol, var) in def.pattern_vars { vars_by_symbol.insert(symbol, var); } } + + dep_constraints.push(valid_dep.constraint); } broken @ FileProblem { .. } => { @@ -413,9 +418,9 @@ pub fn solve_loaded( } } - for constraint in constraints { - solve::run(&vars_by_symbol, problems, subs, &constraint); + for dep_constraint in dep_constraints { + solve::run(&vars_by_symbol, problems, subs, &dep_constraint); } - solve::run(&vars_by_symbol, problems, subs, &module_constraint); + solve::run(&vars_by_symbol, problems, subs, &module.constraint); } diff --git a/src/uniqueness/mod.rs b/src/uniqueness/mod.rs index 81fa5777ed..2998f00203 100644 --- a/src/uniqueness/mod.rs +++ b/src/uniqueness/mod.rs @@ -33,7 +33,6 @@ pub struct Env { pub procedures: ImMap, } -#[allow(clippy::too_many_arguments)] pub fn canonicalize_declaration( var_store: &VarStore, region: Region, @@ -97,7 +96,7 @@ fn canonicalize_pattern( )); } - ExactString(_) => { + StrLiteral(_) => { state.constraints.push(Constraint::Pattern( pattern.region, PatternCategory::Str, @@ -106,33 +105,33 @@ fn canonicalize_pattern( )); } - RecordDestructure(patterns) => { - let ext_var = var_store.fresh(); - let ext_type = Type::Variable(ext_var); + RecordDestructure(ext_var, patterns) => { + let ext_type = Type::Variable(*ext_var); let mut field_types: SendMap = SendMap::default(); - for (pattern, maybe_guard) in patterns { - let pat_var = var_store.fresh(); - let pat_type = Type::Variable(pat_var); + for (pat_var, label, symbol, maybe_guard) in patterns { + let pat_type = Type::Variable(*pat_var); let pattern_expected = PExpected::NoExpectation(pat_type.clone()); - if let Some(loc_guard) = maybe_guard { - canonicalize_pattern(var_store, state, pattern, pattern_expected.clone()); - canonicalize_pattern(var_store, state, loc_guard, pattern_expected); - } else { - canonicalize_pattern(var_store, state, pattern, pattern_expected); + match maybe_guard { + Some((_guard_var, loc_guard)) => { + state.headers.insert( + symbol.clone(), + Located { + region: pattern.region, + value: pat_type.clone(), + }, + ); + + canonicalize_pattern(var_store, state, loc_guard, pattern_expected); + } + None => { + canonicalize_pattern(var_store, state, pattern, pattern_expected); + } } - let name = if let Identifier(n) = &pattern.value { - let a: Box = n.clone().into(); - let b: Lowercase = a.into(); - b - } else { - unreachable!("the lhs must be an identifier at this point"); - }; - - state.vars.push(pat_var); - field_types.insert(name, pat_type); + state.vars.push(*pat_var); + field_types.insert(label.clone(), pat_type); } let record_type = @@ -292,22 +291,25 @@ pub fn canonicalize_expr( (output, And(constraints)) } } - Var(variable, symbol) => { - var_usage.register(symbol); - match var_usage.get_usage(symbol) { + Var { + symbol_for_lookup, .. + } => { + var_usage.register(symbol_for_lookup); + match var_usage.get_usage(symbol_for_lookup) { Some(sharing::ReferenceCount::Shared) => { // the variable is used/consumed more than once, so it must be Shared - let val_var = *variable; + let val_var = var_store.fresh(); let uniq_var = var_store.fresh(); let val_type = Variable(val_var); let uniq_type = Variable(uniq_var); + let attr_type = constrain::attr_type(uniq_type.clone(), val_type); ( Output::default(), And(vec![ - Lookup(symbol.clone(), expected.clone(), region), + Lookup(symbol_for_lookup.clone(), expected.clone(), region), Eq(attr_type, expected, region), Eq( uniq_type, @@ -321,24 +323,14 @@ pub fn canonicalize_expr( // no additional constraints, keep uniqueness unbound ( Output::default(), - Lookup(symbol.clone(), expected.clone(), region), + Lookup(symbol_for_lookup.clone(), expected.clone(), region), ) } None => panic!("symbol not analyzed"), } } - /* - FunctionPointer(_variable, symbol) => match env.bound_names.get(symbol) { - // constraint expected ~ the type of this symbol in the environment - None => panic!("FunctionPointer: no variable for {:?}", symbol), - Some(var) => Output::new(Eq(Variable(*var), expected, Region::zero())), - }, - */ - FunctionPointer(_, _) => { - panic!("TODO implement function pointer?"); - } Closure(_symbol, _recursion, args, boxed_body) => { - let (ret_var, body) = &**boxed_body; + let (body, ret_var) = &**boxed_body; // first, generate constraints for the arguments let mut arg_types = Vec::new(); @@ -409,13 +401,12 @@ pub fn canonicalize_expr( (output, constraint) } - Call(fn_expr, loc_args, _) => { - let fn_var = var_store.fresh(); - let fn_type = Variable(fn_var); - let ret_var = var_store.fresh(); - let ret_type = Variable(ret_var); + Call(boxed, loc_args, _) => { + let (fn_var, fn_expr, ret_var) = &**boxed; + let fn_type = Variable(*fn_var); + let ret_type = Variable(*ret_var); let fn_expected = Expected::NoExpectation(fn_type.clone()); - let fn_region = Region::zero(); + let fn_region = fn_expr.region; let mut vars = Vec::with_capacity(2 + loc_args.len()); @@ -425,7 +416,7 @@ pub fn canonicalize_expr( var_store, var_usage, fn_region, - &fn_expr, + &fn_expr.value, fn_expected, ); @@ -484,8 +475,13 @@ pub fn canonicalize_expr( Output::default(), can_defs(rigids, var_store, var_usage, defs, expected, loc_ret), ), - When(variable, loc_cond, branches) => { - let cond_var = *variable; + When { + cond_var, + loc_cond, + branches, + .. + } => { + let cond_var = *cond_var; let cond_type = Variable(cond_var); let (mut output, expr_con) = canonicalize_expr( rigids, @@ -502,9 +498,7 @@ pub fn canonicalize_expr( match expected { Expected::FromAnnotation(name, arity, _, typ) => { - for (index, ((_patter_var, loc_pattern), (_expr_var, loc_expr))) in - branches.iter().enumerate() - { + for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() { let mut branch_var_usage = old_var_usage.clone(); let branch_con = canonicalize_when_branch( var_store, @@ -554,9 +548,7 @@ pub fn canonicalize_expr( let branch_type = Variable(branch_var); let mut branch_cons = Vec::with_capacity(branches.len()); - for (index, ((_pattern_var, loc_pattern), (_expr_var, loc_expr))) in - branches.iter().enumerate() - { + for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() { let mut branch_var_usage = old_var_usage.clone(); let branch_con = canonicalize_when_branch( var_store, @@ -778,7 +770,7 @@ fn can_defs( constraints: Vec::with_capacity(1), }; - canonicalize_pattern(var_store, &mut state, &def.pattern, pattern_expected); + canonicalize_pattern(var_store, &mut state, &def.loc_pattern, pattern_expected); flex_info.vars.push(pattern_var); @@ -788,19 +780,19 @@ fn can_defs( rigids, var_store, var_usage, - def.expr.region, - &def.expr.value, + def.loc_expr.region, + &def.loc_expr.value, Expected::NoExpectation(expr_type.clone()), ); add_pattern_to_lookup_types( // TODO can we we avoid this clone? - def.pattern.clone(), + def.loc_pattern.clone(), &mut flex_info.def_types, expr_type.clone(), ); - bound_symbols.extend(pattern::symbols_from_pattern(&def.pattern.value)); + bound_symbols.extend(pattern::symbols_from_pattern(&def.loc_pattern.value)); flex_info.constraints.push(Let(Box::new(LetConstraint { rigid_vars: Vec::new(), diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index 7ee8def6aa..fafa196b2a 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -9,6 +9,7 @@ use roc::can::problem::Problem; use roc::can::scope::Scope; use roc::can::symbol::Symbol; use roc::collections::{ImMap, MutMap, SendSet}; +use roc::constrain::expr::constrain_expr; use roc::ident::Ident; use roc::parse; use roc::parse::ast::{self, Attempting}; @@ -191,13 +192,18 @@ pub fn can_expr_with( let scope_prefix = format!("{}.{}$", home, name).into(); let mut scope = Scope::new(scope_prefix, declared_idents.clone()); let mut env = Env::new(home.into()); - let (loc_expr, output, constraint) = canonicalize_expr( - &ImMap::default(), + let (loc_expr, output) = canonicalize_expr( &mut env, &var_store, &mut scope, Region::zero(), &loc_expr.value, + ); + + let constraint = constrain_expr( + &ImMap::default(), + loc_expr.region, + &loc_expr.value, expected, ); diff --git a/tests/test_canonicalize.rs b/tests/test_canonicalize.rs index fcde37a8df..8c343aca87 100644 --- a/tests/test_canonicalize.rs +++ b/tests/test_canonicalize.rs @@ -62,6 +62,33 @@ mod test_canonicalize { assert_eq!(actual.value, expected); } + fn assert_can_float(input: &str, expected: f64) { + let arena = Bump::new(); + let (loc_actual, _, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default()); + + match loc_actual.value { + Expr::Float(_, actual) => { + assert_eq!(expected, actual); + } + actual => { + panic!("Expected a Float, but got: {:?}", actual); + } + } + } + fn assert_can_int(input: &str, expected: i64) { + let arena = Bump::new(); + let (loc_actual, _, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default()); + + match loc_actual.value { + Expr::Int(_, actual) => { + assert_eq!(expected, actual); + } + actual => { + panic!("Expected an Int, but got: {:?}", actual); + } + } + } + // NUMBER LITERALS #[test] @@ -106,67 +133,67 @@ mod test_canonicalize { #[test] fn zero() { - assert_can("0", Int(0)); + assert_can_int("0", 0); } #[test] fn minus_zero() { - assert_can("-0", Int(0)); + assert_can_int("-0", 0); } #[test] fn zero_point_zero() { - assert_can("0.0", Float(0.0)); + assert_can_float("0.0", 0.0); } #[test] fn minus_zero_point_zero() { - assert_can("-0.0", Float(-0.0)); + assert_can_float("-0.0", -0.0); } #[test] fn hex_zero() { - assert_can("0x0", Int(0x0)); + assert_can_int("0x0", 0x0); } #[test] fn hex_one_b() { - assert_can("0x1b", Int(0x1b)); + assert_can_int("0x1b", 0x1b); } #[test] fn minus_hex_one_b() { - assert_can("-0x1b", Int(-0x1b)); + assert_can_int("-0x1b", -0x1b); } #[test] fn octal_zero() { - assert_can("0o0", Int(0o0)); + assert_can_int("0o0", 0o0); } #[test] fn octal_one_two() { - assert_can("0o12", Int(0o12)); + assert_can_int("0o12", 0o12); } #[test] fn minus_octal_one_two() { - assert_can("-0o12", Int(-0o12)); + assert_can_int("-0o12", -0o12); } #[test] fn binary_zero() { - assert_can("0b0", Int(0b0)); + assert_can_int("0b0", 0b0); } #[test] fn binary_one_one() { - assert_can("0b11", Int(0b11)); + assert_can_int("0b11", 0b11); } #[test] fn minus_binary_one_one() { - assert_can("-0b11", Int(-0b11)); + assert_can_int("-0b11", -0b11); } // LOCALS @@ -232,7 +259,7 @@ mod test_canonicalize { fn get_closure(expr: &Expr, i: usize) -> roc::can::expr::Recursive { match expr { - Defs(assignments, _) => match &assignments.get(i).map(|def| &def.expr.value) { + Defs(assignments, _) => match &assignments.get(i).map(|def| &def.loc_expr.value) { Some(Closure(_, recursion, _, _)) => recursion.clone(), Some(other @ _) => { panic!("assignment at {} is not a closure, but a {:?}", i, other) diff --git a/tests/test_infer.rs b/tests/test_infer.rs index bb3c801573..47ae2cdd43 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -908,7 +908,7 @@ mod test_infer { infer_eq( indoc!( r#" - foo : Int -> Bool + foo: Int -> Bool foo 2 "# diff --git a/tests/test_load.rs b/tests/test_load.rs index 668dfd1b25..a2183c96fb 100644 --- a/tests/test_load.rs +++ b/tests/test_load.rs @@ -171,7 +171,7 @@ mod test_load { assert_eq!(expected_types.len(), module.defs.len()); for def in module.defs { - for (symbol, expr_var) in def.vars_by_symbol { + for (symbol, expr_var) in def.pattern_vars { let content = subs.get(expr_var).content; name_all_type_vars(expr_var, &mut subs); diff --git a/tests/test_uniqueness_infer.rs b/tests/test_uniqueness_infer.rs index 6a9f290077..2b97065e10 100644 --- a/tests/test_uniqueness_infer.rs +++ b/tests/test_uniqueness_infer.rs @@ -888,9 +888,9 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - when foo is - { x: 4 }-> x - "# + when foo is + { x: 4 }-> x + "# ), "Int", );