From 28ba645121a0abc8632e64c65d1228d63ae0dd06 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 11 Apr 2021 21:02:31 +0200 Subject: [PATCH 1/6] parse underscores in expressions --- compiler/can/src/expr.rs | 4 ++ compiler/can/src/operator.rs | 1 + compiler/fmt/src/expr.rs | 5 +++ compiler/parse/src/ast.rs | 2 + compiler/parse/src/expr.rs | 22 +++++++++++ compiler/parse/src/parser.rs | 1 + compiler/parse/tests/test_parse.rs | 60 ++++++++++++++++++++++++++---- 7 files changed, 87 insertions(+), 8 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index c1bdced3b8..90fdfbdc4b 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -409,6 +409,10 @@ pub fn canonicalize_expr<'a>( ast::Expr::Var { module_name, ident } => { canonicalize_lookup(env, scope, module_name, ident, region) } + ast::Expr::Underscore(_) => { + // we parse underscored, but they are not valid expression syntax + todo!("todo") + } ast::Expr::Defs(loc_defs, loc_ret) => { can_defs_with_return( env, diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index d1220c14d5..96ba407aaf 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -119,6 +119,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a | Str(_) | AccessorFunction(_) | Var { .. } + | Underscore { .. } | MalformedIdent(_, _) | MalformedClosure | PrecedenceConflict { .. } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 41fc6592d0..72234f21a3 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -30,6 +30,7 @@ impl<'a> Formattable<'a> for Expr<'a> { | Access(_, _) | AccessorFunction(_) | Var { .. } + | Underscore { .. } | MalformedIdent(_, _) | MalformedClosure | GlobalTag(_) @@ -189,6 +190,10 @@ impl<'a> Formattable<'a> for Expr<'a> { buf.push_str(ident); } + Underscore(name) => { + buf.push('_'); + buf.push_str(name); + } Apply(loc_expr, loc_args, _) => { if apply_needs_parens { buf.push('('); diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 0118ff379c..b552455be1 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -115,6 +115,8 @@ pub enum Expr<'a> { ident: &'a str, }, + Underscore(&'a str), + // Tags GlobalTag(&'a str), PrivateTag(&'a str), diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 371155b79b..ea594f3a2e 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -203,6 +203,7 @@ fn parse_loc_term<'a>( loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())), loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), + loc!(underscore_expression()), loc!(record_literal_help(min_indent)), loc!(specialize(EExpr::List, list_literal_help(min_indent))), loc!(map_with_arena!( @@ -213,6 +214,26 @@ fn parse_loc_term<'a>( .parse(arena, state) } +fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { + move |arena: &'a Bump, state: State<'a>| { + let (_, _, next_state) = word1(b'_', EExpr::Underscore).parse(arena, state)?; + + let lowercase_ident_expr = { + let row = state.line; + let col = state.column; + + specialize(move |_, _, _| EExpr::End(row, col), lowercase_ident()) + }; + + let (_, output, final_state) = optional(lowercase_ident_expr).parse(arena, next_state)?; + + match output { + Some(name) => Ok((MadeProgress, Expr::Underscore(name), final_state)), + None => Ok((MadeProgress, Expr::Underscore(&""), final_state)), + } + } +} + fn loc_possibly_negative_or_negated_term<'a>( min_indent: u16, options: ExprParseOptions, @@ -1316,6 +1337,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::Underscore(opt_name)), Expr::GlobalTag(value) => Ok(Pattern::GlobalTag(value)), Expr::PrivateTag(value) => Ok(Pattern::PrivateTag(value)), Expr::Apply(loc_val, loc_args, _) => { diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index d769b95b33..4d758516f8 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -411,6 +411,7 @@ pub enum EExpr<'a> { If(If<'a>, Row, Col), Lambda(ELambda<'a>, Row, Col), + Underscore(Row, Col), InParens(EInParens<'a>, Row, Col), Record(ERecord<'a>, Row, Col), diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 34c78a18df..1cfa25c6e0 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -1573,7 +1573,7 @@ mod test_parse { #[test] fn single_underscore_closure() { let arena = Bump::new(); - let pattern = Located::new(0, 0, 1, 2, Underscore(&"")); + let pattern = Located::new(0, 0, 1, 2, Pattern::Underscore(&"")); let patterns = &[pattern]; let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); let actual = parse_expr_with(&arena, "\\_ -> 42"); @@ -1635,8 +1635,8 @@ mod test_parse { #[test] fn closure_with_underscores() { let arena = Bump::new(); - let underscore1 = Located::new(0, 0, 1, 2, Underscore(&"")); - let underscore2 = Located::new(0, 0, 4, 9, Underscore(&"name")); + let underscore1 = Located::new(0, 0, 1, 2, Pattern::Underscore(&"")); + let underscore2 = Located::new(0, 0, 4, 9, Pattern::Underscore(&"name")); let patterns = bumpalo::vec![in &arena; underscore1, underscore2]; let expected = Closure( arena.alloc(patterns), @@ -1866,6 +1866,49 @@ mod test_parse { ); } + #[test] + fn underscore_backpassing() { + let arena = Bump::new(); + let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let underscore = Located::new(1, 1, 0, 1, Pattern::Underscore(&"")); + let identifier_y = Located::new(1, 1, 7, 8, Identifier("y")); + + let num_4 = Num("4"); + + let var_y = Var { + module_name: "", + ident: "y", + }; + let loc_var_y = arena.alloc(Located::new(1, 1, 12, 13, var_y)); + + let closure = ParensAround(arena.alloc(Closure(arena.alloc([identifier_y]), loc_var_y))); + let loc_closure = Located::new(1, 1, 5, 14, closure); + + let ret = Expr::SpaceBefore(arena.alloc(num_4), newlines.into_bump_slice()); + let loc_ret = Located::new(3, 3, 0, 1, ret); + + let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; + let expected = Expr::SpaceBefore( + arena.alloc(Expr::Backpassing( + arena.alloc([underscore]), + arena.alloc(loc_closure), + arena.alloc(loc_ret), + )), + reset_indentation.into_bump_slice(), + ); + + assert_parses_to( + indoc!( + r#"# leading comment + _ <- (\y -> y) + + 4 + "# + ), + expected, + ); + } + #[test] fn two_backpassing() { let arena = Bump::new(); @@ -2624,7 +2667,7 @@ mod test_parse { guard: None, }); let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore("")), newlines); + let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines); let loc_pattern2 = Located::new(3, 3, 4, 5, pattern2); let expr2 = Num("4"); let loc_expr2 = Located::new(3, 3, 9, 10, expr2); @@ -2661,7 +2704,7 @@ mod test_parse { let branch1 = { let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(Underscore("")), newlines); + let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines); let loc_pattern1 = Located::new(1, 1, 4, 5, pattern1); let num_1 = Num("1"); let expr1 = Located::new( @@ -2680,7 +2723,8 @@ mod test_parse { }; let branch2 = { - let pattern1 = Pattern::SpaceBefore(arena.alloc(Underscore("")), &[Newline, Newline]); + let pattern1 = + Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), &[Newline, Newline]); let loc_pattern1 = Located::new(4, 4, 4, 5, pattern1); let num_1 = Num("2"); let expr1 = Located::new( @@ -3576,7 +3620,7 @@ mod test_parse { guard: None, }); let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore(&"")), newlines); + let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore(&"")), newlines); let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2); let expr2 = Num("4"); let loc_expr2 = Located::new(2, 2, 9, 10, expr2); @@ -3621,7 +3665,7 @@ mod test_parse { guard: None, }); let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore(&"")), newlines); + let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore(&"")), newlines); let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2); let expr2 = Num("4"); let loc_expr2 = Located::new(2, 2, 9, 10, expr2); From 46ab922dd134b210792d618a4ed2feac6b38dd95 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 11 Apr 2021 22:18:51 +0200 Subject: [PATCH 2/6] throw error in canonicalization --- compiler/can/src/expr.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 90fdfbdc4b..c7cf06c666 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -409,9 +409,17 @@ pub fn canonicalize_expr<'a>( ast::Expr::Var { module_name, ident } => { canonicalize_lookup(env, scope, module_name, ident, region) } - ast::Expr::Underscore(_) => { - // we parse underscored, but they are not valid expression syntax - todo!("todo") + ast::Expr::Underscore(name) => { + // we parse underscores, but they are not valid expression syntax + let problem = roc_problem::can::RuntimeError::MalformedIdentifier( + (*name).into(), + roc_parse::ident::BadIdent::Underscore(region.start_line, region.start_col), + region, + ); + + env.problem(Problem::RuntimeError(problem.clone())); + + (RuntimeError(problem), Output::default()) } ast::Expr::Defs(loc_defs, loc_ret) => { can_defs_with_return( From 2bb1f2cca77266292f125919de43c4ebd004ffd3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 12 Apr 2021 20:48:58 +0200 Subject: [PATCH 3/6] improve error reporting for underscore expr --- compiler/can/src/pattern.rs | 18 ++++++++++-- compiler/parse/src/expr.rs | 30 ++++++++++++++++---- compiler/problem/src/can.rs | 8 +++++- compiler/reporting/src/error/canonicalize.rs | 8 ++++-- compiler/reporting/tests/test_reporting.rs | 23 +++++++++++++++ editor/src/lang/pattern.rs | 18 ++++++++++-- 6 files changed, 93 insertions(+), 12 deletions(-) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index a150befc24..28ea70f9e5 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -198,7 +198,7 @@ pub fn canonicalize_pattern<'a>( Underscore(_) => match pattern_type { WhenBranch | FunctionArg => Pattern::Underscore, - ptype => unsupported_pattern(env, ptype, region), + TopLevelDef | DefExpr => bad_underscore(env, region), }, NumLiteral(string) => match pattern_type { @@ -402,7 +402,21 @@ pub fn canonicalize_pattern<'a>( /// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't /// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern. fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region) -> Pattern { - env.problem(Problem::UnsupportedPattern(pattern_type, region)); + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::Unsupported(pattern_type), + region, + )); + + Pattern::UnsupportedPattern(region) +} + +fn bad_underscore(env: &mut Env, region: Region) -> Pattern { + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::UnderscoreInDef, + region, + )); Pattern::UnsupportedPattern(region) } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index ea594f3a2e..084e50be31 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -192,7 +192,9 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> { ) } -fn parse_loc_term<'a>( +/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a +/// pattern later +fn parse_loc_term_or_underscore<'a>( min_indent: u16, options: ExprParseOptions, arena: &'a Bump, @@ -214,6 +216,27 @@ fn parse_loc_term<'a>( .parse(arena, state) } +fn parse_loc_term<'a>( + min_indent: u16, + options: ExprParseOptions, + arena: &'a Bump, + state: State<'a>, +) -> ParseResult<'a, Located>, EExpr<'a>> { + one_of!( + loc_expr_in_parens_etc_help(min_indent), + loc!(specialize(EExpr::Str, string_literal_help())), + loc!(specialize(EExpr::Number, positive_number_literal_help())), + loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), + loc!(record_literal_help(min_indent)), + loc!(specialize(EExpr::List, list_literal_help(min_indent))), + loc!(map_with_arena!( + assign_or_destructure_identifier(), + ident_to_expr + )), + ) + .parse(arena, state) +} + fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { move |arena: &'a Bump, state: State<'a>| { let (_, _, next_state) = word1(b'_', EExpr::Underscore).parse(arena, state)?; @@ -264,10 +287,7 @@ fn loc_possibly_negative_or_negated_term<'a>( ) } )), - |arena, state| { - // TODO use parse_loc_term_better - parse_loc_term(min_indent, options, arena, state) - } + |arena, state| { parse_loc_term_or_underscore(min_indent, options, arena, state) } ] } diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index b3c066badb..8644c037f8 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -14,6 +14,12 @@ pub struct CycleEntry { pub expr_region: Region, } +#[derive(Clone, Debug, PartialEq)] +pub enum BadPattern { + UnderscoreInDef, + Unsupported(PatternType), +} + /// Problems that can occur in the course of canonicalization. #[derive(Clone, Debug, PartialEq)] pub enum Problem { @@ -25,7 +31,7 @@ pub enum Problem { UnusedArgument(Symbol, Symbol, Region), PrecedenceProblem(PrecedenceProblem), // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! - UnsupportedPattern(PatternType, Region), + UnsupportedPattern(BadPattern, Region), ShadowingInAnnotation { original_region: Region, shadow: Located, diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index 453c3fad16..54d3aef773 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -2,7 +2,7 @@ use roc_collections::all::MutSet; use roc_module::ident::Lowercase; use roc_parse::parser::{Col, Row}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; -use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError}; +use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_region::all::Region; use std::path::PathBuf; @@ -103,7 +103,11 @@ pub fn can_problem<'b>( }, alloc.region(region), ]), - Problem::UnsupportedPattern(pattern_type, region) => { + Problem::UnsupportedPattern(BadPattern::UnderscoreInDef, region) => alloc.stack(vec![ + alloc.reflow("Underscore patterns are not allowed in definitions"), + alloc.region(region), + ]), + Problem::UnsupportedPattern(BadPattern::Unsupported(pattern_type), region) => { use roc_parse::pattern::PatternType::*; let this_thing = match pattern_type { diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index d8407e4dd2..7040cce5ea 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -6334,4 +6334,27 @@ mod test_reporting { ), ) } + + #[test] + fn underscore_let() { + report_problem_as( + indoc!( + r#" + _ = 3 + + 4 + "# + ), + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + Underscore patterns are not allowed in definitions + + 1│ _ = 3 + ^ + "# + ), + ) + } } diff --git a/editor/src/lang/pattern.rs b/editor/src/lang/pattern.rs index dab08d384e..e3f6dc8160 100644 --- a/editor/src/lang/pattern.rs +++ b/editor/src/lang/pattern.rs @@ -153,7 +153,7 @@ pub fn to_pattern2<'a>( Underscore(_) => match pattern_type { WhenBranch | FunctionArg => Pattern2::Underscore, - ptype => unsupported_pattern(env, ptype, region), + TopLevelDef | DefExpr => underscore_in_def(env, region), }, FloatLiteral(ref string) => match pattern_type { @@ -521,7 +521,21 @@ fn unsupported_pattern<'a>( pattern_type: PatternType, region: Region, ) -> Pattern2 { - env.problem(Problem::UnsupportedPattern(pattern_type, region)); + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::Unsupported(pattern_type), + region, + )); + + Pattern2::UnsupportedPattern(region) +} + +fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::UnderscoreInDef, + region, + )); Pattern2::UnsupportedPattern(region) } From 791fda0270b53f6892b4f445277efc30c06d15bf Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 12 Apr 2021 20:49:48 +0200 Subject: [PATCH 4/6] update cli example --- examples/cli/HttpGet.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/HttpGet.roc b/examples/cli/HttpGet.roc index 93c60f30ec..ca06317d62 100644 --- a/examples/cli/HttpGet.roc +++ b/examples/cli/HttpGet.roc @@ -5,7 +5,7 @@ app "http-get" main : Task.Task {} * main = - {} <- await (Stdout.line "What URL should I get?") + _ <- await (Stdout.line "What URL should I get?") url <- await Stdin.line From bffb9ab6feac03488ef55c1819536f0bc37dc193 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 12 Apr 2021 21:26:20 +0200 Subject: [PATCH 5/6] initial solver for the editor's type constraints --- Cargo.lock | 1 + compiler/can/src/expected.rs | 12 + compiler/types/src/types.rs | 2 +- editor/Cargo.toml | 1 + editor/src/lang/mod.rs | 1 + editor/src/lang/solve.rs | 1575 ++++++++++++++++++++++++++++++++++ editor/tests/solve_expr2.rs | 37 +- 7 files changed, 1625 insertions(+), 4 deletions(-) create mode 100644 editor/src/lang/solve.rs diff --git a/Cargo.lock b/Cargo.lock index 2b39228b43..b35900628a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3060,6 +3060,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_types", + "roc_unify", "ropey", "serde", "snafu", diff --git a/compiler/can/src/expected.rs b/compiler/can/src/expected.rs index 4075624dcc..fc9b0e37de 100644 --- a/compiler/can/src/expected.rs +++ b/compiler/can/src/expected.rs @@ -89,4 +89,16 @@ impl Expected { } } } + + pub fn replace_ref(&self, new: U) -> Expected { + match self { + Expected::NoExpectation(_val) => Expected::NoExpectation(new), + Expected::ForReason(reason, _val, region) => { + Expected::ForReason(reason.clone(), new, *region) + } + Expected::FromAnnotation(pattern, size, source, _val) => { + Expected::FromAnnotation(pattern.clone(), *size, *source, new) + } + } + } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index b95ea6eaaf..bee6d7ef66 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -858,7 +858,7 @@ pub enum PReason { OptionalField, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AnnotationSource { TypedIfBranch { index: Index, num_branches: usize }, TypedWhenBranch { index: Index }, diff --git a/editor/Cargo.toml b/editor/Cargo.toml index d0deb0a552..d20bfaf82c 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -15,6 +15,7 @@ roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } +roc_unify = { path = "../compiler/unify" } roc_fmt = { path = "../compiler/fmt" } roc_reporting = { path = "../compiler/reporting" } roc_solve = { path = "../compiler/solve" } diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs index 7fd9f2e926..dcb9c6dae1 100644 --- a/editor/src/lang/mod.rs +++ b/editor/src/lang/mod.rs @@ -7,4 +7,5 @@ mod pattern; pub mod pool; pub mod roc_file; pub mod scope; +pub mod solve; pub mod types; diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs new file mode 100644 index 0000000000..e35ff0ce56 --- /dev/null +++ b/editor/src/lang/solve.rs @@ -0,0 +1,1575 @@ +use crate::lang::constrain::Constraint::{self, *}; +use crate::lang::types::Type2; +use roc_can::expected::{Expected, PExpected}; +use roc_collections::all::{ImMap, MutMap}; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; +use roc_types::solved_types::Solved; +use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; +use roc_types::types::Type; +use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField}; +use roc_unify::unify::unify; +use roc_unify::unify::Unified::*; + +// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed +// https://github.com/elm/compiler +// Thank you, Evan! + +// A lot of energy was put into making type inference fast. That means it's pretty intimidating. +// +// Fundamentally, type inference assigns very general types based on syntax, and then tries to +// make all the pieces fit together. For instance when writing +// +// > f x +// +// We know that `f` is a function, and thus must have some type `a -> b`. +// `x` is just a variable, that gets the type `c` +// +// Next comes constraint generation. For `f x` to be well-typed, +// it must be the case that `c = a`, So a constraint `Eq(c, a)` is generated. +// But `Eq` is a bit special: `c` does not need to equal `a` exactly, but they need to be equivalent. +// This allows for instance the use of aliases. `c` could be an alias, and so looks different from +// `a`, but they still represent the same type. +// +// Then we get to solving, which happens in this file. +// +// When we hit an `Eq` constraint, then we check whether the two involved types are in fact +// equivalent using unification, and when they are, we can substitute one for the other. +// +// When all constraints are processed, and no unification errors have occurred, then the program +// is type-correct. Otherwise the errors are reported. +// +// Now, coming back to efficiency, this type checker uses *ranks* to optimize +// The rank tracks the number of let-bindings a variable is "under". Top-level definitions +// have rank 1. A let in a top-level definition gets rank 2, and so on. +// +// This has to do with generalization of type variables. This is described here +// +// http://okmij.org/ftp/ML/generalization.html#levels +// +// The problem is that when doing inference naively, this program would fail to typecheck +// +// f = +// id = \x -> x +// +// { a: id 1, b: id "foo" } +// +// Because `id` is applied to an integer, the type `Int -> Int` is inferred, which then gives a +// type error for `id "foo"`. +// +// Thus instead the inferred type for `id` is generalized (see the `generalize` function) to `a -> a`. +// Ranks are used to limit the number of type variables considered for generalization. Only those inside +// of the let (so those used in inferring the type of `\x -> x`) are considered. + +#[derive(PartialEq, Debug, Clone)] +pub enum TypeError { + BadExpr(Region, Category, ErrorType, Expected), + BadPattern(Region, PatternCategory, ErrorType, PExpected), + CircularType(Region, Symbol, ErrorType), + BadType(roc_types::types::Problem), + UnexposedLookup(Symbol), +} + +#[derive(Clone, Debug, Default)] +pub struct Env { + pub vars_by_symbol: MutMap, + pub aliases: MutMap, +} + +const DEFAULT_POOLS: usize = 8; + +#[derive(Clone, Debug)] +struct Pools(Vec>); + +impl Default for Pools { + fn default() -> Self { + Pools::new(DEFAULT_POOLS) + } +} + +impl Pools { + pub fn new(num_pools: usize) -> Self { + Pools(vec![Vec::new(); num_pools]) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { + self.0 + .get_mut(rank.into_usize()) + .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) + } + + pub fn get(&self, rank: Rank) -> &Vec { + self.0 + .get(rank.into_usize()) + .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) + } + + pub fn iter(&self) -> std::slice::Iter<'_, Vec> { + self.0.iter() + } + + pub fn split_last(&self) -> (&Vec, &[Vec]) { + self.0 + .split_last() + .unwrap_or_else(|| panic!("Attempted to split_last() on non-empy Pools")) + } + + pub fn extend_to(&mut self, n: usize) { + for _ in self.len()..n { + self.0.push(Vec::new()); + } + } +} + +#[derive(Clone)] +struct State { + env: Env, + mark: Mark, +} + +pub fn run( + env: &Env, + problems: &mut Vec, + mut subs: Subs, + constraint: &Constraint, +) -> (Solved, Env) { + let env = run_in_place(env, problems, &mut subs, constraint); + + (Solved(subs), env) +} + +/// Modify an existing subs in-place instead +pub fn run_in_place( + env: &Env, + problems: &mut Vec, + subs: &mut Subs, + constraint: &Constraint, +) -> Env { + let mut pools = Pools::default(); + let state = State { + env: env.clone(), + mark: Mark::NONE.next(), + }; + let rank = Rank::toplevel(); + let state = solve( + env, + state, + rank, + &mut pools, + problems, + &mut MutMap::default(), + subs, + constraint, + ); + + state.env +} + +#[allow(clippy::too_many_arguments)] +fn solve( + env: &Env, + state: State, + rank: Rank, + pools: &mut Pools, + problems: &mut Vec, + cached_aliases: &mut MutMap, + subs: &mut Subs, + constraint: &Constraint, +) -> State { + match constraint { + True => state, + // SaveTheEnvironment => { + // // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment + // let mut copy = state; + // + // copy.env = env.clone(); + // + // copy + // } + Eq(typ, expectation, category, region) => { + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + let expected = type_to_var( + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + category.clone(), + actual_type, + expectation.replace_ref(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } // Store(source, target, _filename, _linenr) => { + // // a special version of Eq that is used to store types in the AST. + // // IT DOES NOT REPORT ERRORS! + // let actual = type_to_var(subs, rank, pools, cached_aliases, source); + // let target = *target; + // + // match unify(subs, actual, target) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // Failure(vars, _actual_type, _expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // BadType(vars, _problem) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // } + // } + // Lookup(symbol, expectation, region) => { + // match env.vars_by_symbol.get(&symbol) { + // Some(var) => { + // // Deep copy the vars associated with this symbol before unifying them. + // // Otherwise, suppose we have this: + // // + // // identity = \a -> a + // // + // // x = identity 5 + // // + // // When we call (identity 5), it's important that we not unify + // // on identity's original vars. If we do, the type of `identity` will be + // // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; + // // the type of `identity` is more general than that! + // // + // // Instead, we want to unify on a *copy* of its vars. If the copy unifies + // // successfully (in this case, to `Int -> Int`), we can use that to + // // infer the type of this lookup (in this case, `Int`) without ever + // // having mutated the original. + // // + // // If this Lookup is targeting a value in another module, + // // then we copy from that module's Subs into our own. If the value + // // is being looked up in this module, then we use our Subs as both + // // the source and destination. + // let actual = deep_copy_var(subs, rank, pools, *var); + // let expected = type_to_var( + // subs, + // rank, + // pools, + // cached_aliases, + // expectation.get_type_ref(), + // ); + // match unify(subs, actual, expected) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // + // Failure(vars, actual_type, expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // let problem = TypeError::BadExpr( + // *region, + // Category::Lookup(*symbol), + // actual_type, + // expectation.clone().replace(expected_type), + // ); + // + // problems.push(problem); + // + // state + // } + // BadType(vars, problem) => { + // introduce(subs, rank, pools, &vars); + // + // problems.push(TypeError::BadType(problem)); + // + // state + // } + // } + // } + // None => { + // problems.push(TypeError::UnexposedLookup(*symbol)); + // + // state + // } + // } + // } + // And(sub_constraints) => { + // let mut state = state; + // + // for sub_constraint in sub_constraints.iter() { + // state = solve( + // env, + // state, + // rank, + // pools, + // problems, + // cached_aliases, + // subs, + // sub_constraint, + // ); + // } + // + // state + // } + // Pattern(region, category, typ, expectation) => { + // let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + // let expected = type_to_var( + // subs, + // rank, + // pools, + // cached_aliases, + // expectation.get_type_ref(), + // ); + // + // match unify(subs, actual, expected) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // Failure(vars, actual_type, expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // let problem = TypeError::BadPattern( + // *region, + // category.clone(), + // actual_type, + // expectation.clone().replace(expected_type), + // ); + // + // problems.push(problem); + // + // state + // } + // BadType(vars, problem) => { + // introduce(subs, rank, pools, &vars); + // + // problems.push(TypeError::BadType(problem)); + // + // state + // } + // } + // } + // Let(let_con) => { + // match &let_con.ret_constraint { + // True if let_con.rigid_vars.is_empty() => { + // introduce(subs, rank, pools, &let_con.flex_vars); + // + // // If the return expression is guaranteed to solve, + // // solve the assignments themselves and move on. + // solve( + // &env, + // state, + // rank, + // pools, + // problems, + // cached_aliases, + // subs, + // &let_con.defs_constraint, + // ) + // } + // ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { + // let state = solve( + // env, + // state, + // rank, + // pools, + // problems, + // cached_aliases, + // subs, + // &let_con.defs_constraint, + // ); + // + // // Add a variable for each def to new_vars_by_env. + // let mut local_def_vars = ImMap::default(); + // + // for (symbol, loc_type) in let_con.def_types.iter() { + // let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); + // + // local_def_vars.insert( + // *symbol, + // Located { + // value: var, + // region: loc_type.region, + // }, + // ); + // } + // + // let mut new_env = env.clone(); + // for (symbol, loc_var) in local_def_vars.iter() { + // if !new_env.vars_by_symbol.contains_key(&symbol) { + // new_env.vars_by_symbol.insert(*symbol, loc_var.value); + // } + // } + // + // let new_state = solve( + // &new_env, + // state, + // rank, + // pools, + // problems, + // cached_aliases, + // subs, + // ret_con, + // ); + // + // for (symbol, loc_var) in local_def_vars { + // check_for_infinite_type(subs, problems, symbol, loc_var); + // } + // + // new_state + // } + // ret_con => { + // let rigid_vars = &let_con.rigid_vars; + // let flex_vars = &let_con.flex_vars; + // + // // work in the next pool to localize header + // let next_rank = rank.next(); + // + // // introduce variables + // for &var in rigid_vars.iter().chain(flex_vars.iter()) { + // subs.set_rank(var, next_rank); + // } + // + // // determine the next pool + // let next_pools; + // if next_rank.into_usize() < pools.len() { + // next_pools = pools + // } else { + // // we should be off by one at this point + // debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); + // pools.extend_to(next_rank.into_usize()); + // next_pools = pools; + // } + // + // let pool: &mut Vec = next_pools.get_mut(next_rank); + // + // // Replace the contents of this pool with rigid_vars and flex_vars + // pool.clear(); + // pool.reserve(rigid_vars.len() + flex_vars.len()); + // pool.extend(rigid_vars.iter()); + // pool.extend(flex_vars.iter()); + // + // // run solver in next pool + // + // // Add a variable for each def to local_def_vars. + // let mut local_def_vars = ImMap::default(); + // + // for (symbol, loc_type) in let_con.def_types.iter() { + // let def_type = &loc_type.value; + // + // let var = + // type_to_var(subs, next_rank, next_pools, cached_aliases, def_type); + // + // local_def_vars.insert( + // *symbol, + // Located { + // value: var, + // region: loc_type.region, + // }, + // ); + // } + // + // // Solve the assignments' constraints first. + // let State { + // env: saved_env, + // mark, + // } = solve( + // &env, + // state, + // next_rank, + // next_pools, + // problems, + // cached_aliases, + // subs, + // &let_con.defs_constraint, + // ); + // + // let young_mark = mark; + // let visit_mark = young_mark.next(); + // let final_mark = visit_mark.next(); + // + // debug_assert_eq!( + // { + // let offenders = next_pools + // .get(next_rank) + // .iter() + // .filter(|var| { + // let current = subs.get_without_compacting( + // roc_types::subs::Variable::clone(var), + // ); + // + // current.rank.into_usize() > next_rank.into_usize() + // }) + // .collect::>(); + // + // let result = offenders.len(); + // + // if result > 0 { + // dbg!(&subs, &offenders, &let_con.def_types); + // } + // + // result + // }, + // 0 + // ); + // + // // pop pool + // generalize(subs, young_mark, visit_mark, next_rank, next_pools); + // + // next_pools.get_mut(next_rank).clear(); + // + // // check that things went well + // debug_assert!({ + // // NOTE the `subs.redundant` check is added for the uniqueness + // // inference, and does not come from elm. It's unclear whether this is + // // a bug with uniqueness inference (something is redundant that + // // shouldn't be) or that it just never came up in elm. + // let failing: Vec<_> = rigid_vars + // .iter() + // .filter(|&var| { + // !subs.redundant(*var) + // && subs.get_without_compacting(*var).rank != Rank::NONE + // }) + // .collect(); + // + // if !failing.is_empty() { + // println!("Rigids {:?}", &rigid_vars); + // println!("Failing {:?}", failing); + // } + // + // failing.is_empty() + // }); + // + // let mut new_env = env.clone(); + // for (symbol, loc_var) in local_def_vars.iter() { + // // when there are duplicates, keep the one from `env` + // if !new_env.vars_by_symbol.contains_key(&symbol) { + // new_env.vars_by_symbol.insert(*symbol, loc_var.value); + // } + // } + // + // // Note that this vars_by_symbol is the one returned by the + // // previous call to solve() + // let temp_state = State { + // env: saved_env, + // mark: final_mark, + // }; + // + // // Now solve the body, using the new vars_by_symbol which includes + // // the assignments' name-to-variable mappings. + // let new_state = solve( + // &new_env, + // temp_state, + // rank, + // next_pools, + // problems, + // cached_aliases, + // subs, + // &ret_con, + // ); + // + // for (symbol, loc_var) in local_def_vars { + // check_for_infinite_type(subs, problems, symbol, loc_var); + // } + // + // new_state + // } + // } + // } + } +} + +fn type_to_var( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + typ: &Type2, +) -> Variable { + type_to_variable(subs, rank, pools, cached, typ) +} + +/// Abusing existing functions for our purposes +/// this is to put a solved type back into subs +pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type2) -> Variable { + let rank = Rank::NONE; + let mut pools = Pools::default(); + let mut cached = MutMap::default(); + + type_to_variable(subs, rank, &mut pools, &mut cached, typ) +} + +fn type_to_variable( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + typ: &Type2, +) -> Variable { + use Type2::*; + + match typ { + Variable(var) => *var, + Apply(symbol, args) => { + let mut arg_vars = Vec::with_capacity(args.len()); + + // for arg in args { + // arg_vars.push(type_to_variable(subs, rank, pools, cached, arg)) + // } + + let flat_type = FlatType::Apply(*symbol, arg_vars); + let content = Content::Structure(flat_type); + + register(subs, rank, pools, content) + } + + other => todo!("not implemented {:?}", &other), + // EmptyRec => Variable::EMPTY_RECORD, + // EmptyTagUnion => Variable::EMPTY_TAG_UNION, + // + // // This case is important for the rank of boolean variables + // Function(args, closure_type, ret_type) => { + // let mut arg_vars = Vec::with_capacity(args.len()); + // + // for arg in args { + // arg_vars.push(type_to_variable(subs, rank, pools, cached, arg)) + // } + // + // let ret_var = type_to_variable(subs, rank, pools, cached, ret_type); + // let closure_var = type_to_variable(subs, rank, pools, cached, closure_type); + // let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); + // + // register(subs, rank, pools, content) + // } + // Record(fields, ext) => { + // let mut field_vars = MutMap::default(); + // + // for (field, field_type) in fields { + // use RecordField::*; + // + // let field_var = match field_type { + // Required(typ) => Required(type_to_variable(subs, rank, pools, cached, typ)), + // Optional(typ) => Optional(type_to_variable(subs, rank, pools, cached, typ)), + // Demanded(typ) => Demanded(type_to_variable(subs, rank, pools, cached, typ)), + // }; + // + // field_vars.insert(field.clone(), field_var); + // } + // + // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + // let new_ext_var = match roc_types::pretty_print::chase_ext_record( + // subs, + // temp_ext_var, + // &mut field_vars, + // ) { + // Ok(()) => Variable::EMPTY_RECORD, + // Err((new, _)) => new, + // }; + // + // let content = Content::Structure(FlatType::Record(field_vars, new_ext_var)); + // + // register(subs, rank, pools, content) + // } + // TagUnion(tags, ext) => { + // let mut tag_vars = MutMap::default(); + // + // for (tag, tag_argument_types) in tags { + // let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); + // + // for arg_type in tag_argument_types { + // tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); + // } + // + // tag_vars.insert(tag.clone(), tag_argument_vars); + // } + // + // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + // let mut ext_tag_vec = Vec::new(); + // let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( + // subs, + // temp_ext_var, + // &mut ext_tag_vec, + // ) { + // Ok(()) => Variable::EMPTY_TAG_UNION, + // Err((new, _)) => new, + // }; + // tag_vars.extend(ext_tag_vec.into_iter()); + // + // let content = Content::Structure(FlatType::TagUnion(tag_vars, new_ext_var)); + // + // register(subs, rank, pools, content) + // } + // RecursiveTagUnion(rec_var, tags, ext) => { + // let mut tag_vars = MutMap::default(); + // + // for (tag, tag_argument_types) in tags { + // let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); + // + // for arg_type in tag_argument_types { + // tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); + // } + // + // tag_vars.insert(tag.clone(), tag_argument_vars); + // } + // + // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + // let mut ext_tag_vec = Vec::new(); + // let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( + // subs, + // temp_ext_var, + // &mut ext_tag_vec, + // ) { + // Ok(()) => Variable::EMPTY_TAG_UNION, + // Err((new, _)) => new, + // }; + // tag_vars.extend(ext_tag_vec.into_iter()); + // + // let content = + // Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var)); + // + // let tag_union_var = register(subs, rank, pools, content); + // + // subs.set_content( + // *rec_var, + // Content::RecursionVar { + // opt_name: None, + // structure: tag_union_var, + // }, + // ); + // + // tag_union_var + // } + // Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL, + // Alias(symbol, args, alias_type) => { + // // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! + // // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) + // // different variables (once for each occurence). The recursion restriction is required + // // for uniqueness types only: recursive aliases "introduce" an unbound uniqueness + // // attribute in the body, when + // // + // // Peano : [ S Peano, Z ] + // // + // // becomes + // // + // // Peano : [ S (Attr u Peano), Z ] + // // + // // This `u` variable can be different between lists, so giving just one variable to + // // this type is incorrect. + // // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable + // let is_recursive = alias_type.is_recursive(); + // let no_args = args.is_empty(); + // /* + // if no_args && !is_recursive { + // if let Some(var) = cached.get(symbol) { + // return *var; + // } + // } + // */ + // + // let mut arg_vars = Vec::with_capacity(args.len()); + // let mut new_aliases = ImMap::default(); + // + // for (arg, arg_type) in args { + // let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + // + // arg_vars.push((arg.clone(), arg_var)); + // new_aliases.insert(arg.clone(), arg_var); + // } + // + // let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + // let content = Content::Alias(*symbol, arg_vars, alias_var); + // + // let result = register(subs, rank, pools, content); + // + // if no_args && !is_recursive { + // // cached.insert(*symbol, result); + // } + // + // result + // } + // HostExposedAlias { + // name: symbol, + // arguments: args, + // actual: alias_type, + // actual_var, + // .. + // } => { + // let mut arg_vars = Vec::with_capacity(args.len()); + // let mut new_aliases = ImMap::default(); + // + // for (arg, arg_type) in args { + // let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + // + // arg_vars.push((arg.clone(), arg_var)); + // new_aliases.insert(arg.clone(), arg_var); + // } + // + // let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + // + // // unify the actual_var with the result var + // // this can be used to access the type of the actual_var + // // to determine its layout later + // // subs.set_content(*actual_var, descriptor.content); + // + // //subs.set(*actual_var, descriptor.clone()); + // let content = Content::Alias(*symbol, arg_vars, alias_var); + // + // let result = register(subs, rank, pools, content); + // + // // We only want to unify the actual_var with the alias once + // // if it's already redirected (and therefore, redundant) + // // don't do it again + // if !subs.redundant(*actual_var) { + // let descriptor = subs.get(result); + // subs.union(result, *actual_var, descriptor); + // } + // + // result + // } + // Erroneous(problem) => { + // let content = Content::Structure(FlatType::Erroneous(problem.clone())); + // + // register(subs, rank, pools, content) + // } + } +} + +fn check_for_infinite_type( + subs: &mut Subs, + problems: &mut Vec, + symbol: Symbol, + loc_var: Located, +) { + let var = loc_var.value; + + while let Some((recursive, _chain)) = subs.occurs(var) { + let description = subs.get(recursive); + let content = description.content; + + // try to make a tag union recursive, see if that helps + match content { + Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + let rec_var = subs.fresh_unnamed_flex_var(); + subs.set_rank(rec_var, description.rank); + subs.set_content( + rec_var, + Content::RecursionVar { + opt_name: None, + structure: recursive, + }, + ); + + let mut new_tags = MutMap::default(); + + for (label, args) in &tags { + let new_args: Vec<_> = args + .iter() + .map(|var| subs.explicit_substitute(recursive, rec_var, *var)) + .collect(); + + new_tags.insert(label.clone(), new_args); + } + + let new_ext_var = subs.explicit_substitute(recursive, rec_var, ext_var); + + let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext_var); + + subs.set_content(recursive, Content::Structure(flat_type)); + } + + _other => circular_error(subs, problems, symbol, &loc_var), + } + } +} + +fn circular_error( + subs: &mut Subs, + problems: &mut Vec, + symbol: Symbol, + loc_var: &Located, +) { + let var = loc_var.value; + let (error_type, _) = subs.var_to_error_type(var); + let problem = TypeError::CircularType(loc_var.region, symbol, error_type); + + subs.set_content(var, Content::Error); + + problems.push(problem); +} + +fn generalize( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + young_rank: Rank, + pools: &mut Pools, +) { + let young_vars = pools.get(young_rank); + let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars); + + // Get the ranks right for each entry. + // Start at low ranks so we only have to pass over the information once. + for (index, table) in rank_table.iter().enumerate() { + for &var in table.iter() { + adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var); + } + } + + let (last_pool, all_but_last_pool) = rank_table.split_last(); + + // For variables that have rank lowerer than young_rank, register them in + // the appropriate old pool if they are not redundant. + for vars in all_but_last_pool { + for &var in vars { + if !subs.redundant(var) { + let rank = subs.get_rank(var); + + pools.get_mut(rank).push(var); + } + } + } + + // For variables with rank young_rank, if rank < young_rank: register in old pool, + // otherwise generalize + for &var in last_pool { + if !subs.redundant(var) { + let desc_rank = subs.get_rank(var); + + if desc_rank < young_rank { + pools.get_mut(desc_rank).push(var); + } else { + subs.set_rank(var, Rank::NONE); + } + } + } +} + +fn pool_to_rank_table( + subs: &mut Subs, + young_mark: Mark, + young_rank: Rank, + young_vars: &[Variable], +) -> Pools { + let mut pools = Pools::new(young_rank.into_usize() + 1); + + // Sort the variables into buckets by rank. + for &var in young_vars.iter() { + let rank = subs.get_rank(var); + subs.set_mark(var, young_mark); + + debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); + pools.get_mut(rank).push(var); + } + + pools +} + +/// Adjust variable ranks such that ranks never increase as you move deeper. +/// This way the outermost rank is representative of the entire structure. +fn adjust_rank( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + group_rank: Rank, + var: Variable, +) -> Rank { + let desc = subs.get(var); + + if desc.mark == young_mark { + let Descriptor { + content, + rank: _, + mark: _, + copy, + } = desc; + + // Mark the variable as visited before adjusting content, as it may be cyclic. + subs.set_mark(var, visit_mark); + + let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content); + + subs.set( + var, + Descriptor { + content, + rank: max_rank, + mark: visit_mark, + copy, + }, + ); + + max_rank + } else if desc.mark == visit_mark { + // nothing changes + desc.rank + } else { + let mut desc = desc; + + let min_rank = group_rank.min(desc.rank); + + // TODO from elm-compiler: how can min_rank ever be group_rank? + desc.rank = min_rank; + desc.mark = visit_mark; + + subs.set(var, desc); + + min_rank + } +} + +fn adjust_rank_content( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + group_rank: Rank, + content: &Content, +) -> Rank { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + match content { + FlexVar(_) | RigidVar(_) | Error => group_rank, + + RecursionVar { .. } => group_rank, + + Structure(flat_type) => { + match flat_type { + Apply(_, args) => { + let mut rank = Rank::toplevel(); + + for var in args { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + } + + rank + } + + Func(arg_vars, closure_var, ret_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ret_var); + + // TODO investigate further. + // + // My theory is that because the closure_var contains variables already + // contained in the signature only, it does not need to be part of the rank + // calculuation + if true { + rank = rank.max(adjust_rank( + subs, + young_mark, + visit_mark, + group_rank, + *closure_var, + )); + } + + for var in arg_vars { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + } + + rank + } + + EmptyRecord => { + // from elm-compiler: THEORY: an empty record never needs to get generalized + Rank::toplevel() + } + + EmptyTagUnion => Rank::toplevel(), + + Record(fields, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for var in fields.values() { + rank = rank.max(adjust_rank( + subs, + young_mark, + visit_mark, + group_rank, + var.into_inner(), + )); + } + + rank + } + + TagUnion(tags, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for var in tags.values().flatten() { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + } + + rank + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var); + rank = rank.max(adjust_rank( + subs, young_mark, visit_mark, group_rank, *ext_var, + )); + + for var in tags.values().flatten() { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + } + + rank + } + + Erroneous(_) => group_rank, + } + } + + Alias(_, args, real_var) => { + let mut rank = Rank::toplevel(); + + for (_, var) in args { + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + } + + // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() + // this theory is not true in Roc! aliases of function types capture the closure var + rank = rank.max(adjust_rank( + subs, young_mark, visit_mark, group_rank, *real_var, + )); + + rank + } + } +} + +/// Introduce some variables to Pools at the given rank. +/// Also, set each of their ranks in Subs to be the given rank. +fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { + let pool: &mut Vec = pools.get_mut(rank); + + for &var in vars.iter() { + subs.set_rank(var, rank); + } + + pool.extend(vars); +} + +/// Function that converts rigids variables to flex variables +/// this is used during the monomorphization process +pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { + let rank = Rank::NONE; + let mut pools = Pools::default(); + + instantiate_rigids_help(subs, rank, &mut pools, var); +} + +fn instantiate_rigids_help( + subs: &mut Subs, + max_rank: Rank, + pools: &mut Pools, + var: Variable, +) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + let desc = subs.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + return copy; + } + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let content = desc.content; + let copy = var; + + pools.get_mut(max_rank).push(copy); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + subs.set( + var, + Descriptor { + content: content.clone(), + rank: desc.rank, + mark: Mark::NONE, + copy: copy.into(), + }, + ); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match content { + Structure(flat_type) => { + let new_flat_type = match flat_type { + Apply(symbol, args) => { + let args = args + .into_iter() + .map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) + .collect(); + + Apply(symbol, args) + } + + Func(arg_vars, closure_var, ret_var) => { + let new_ret_var = instantiate_rigids_help(subs, max_rank, pools, ret_var); + let new_closure_var = + instantiate_rigids_help(subs, max_rank, pools, closure_var); + let arg_vars = arg_vars + .into_iter() + .map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) + .collect(); + + Func(arg_vars, new_closure_var, new_ret_var) + } + + same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, + + Record(fields, ext_var) => { + let mut new_fields = MutMap::default(); + + for (label, field) in fields { + use RecordField::*; + + let new_field = match field { + Demanded(var) => { + Demanded(instantiate_rigids_help(subs, max_rank, pools, var)) + } + Required(var) => { + Required(instantiate_rigids_help(subs, max_rank, pools, var)) + } + Optional(var) => { + Optional(instantiate_rigids_help(subs, max_rank, pools, var)) + } + }; + + new_fields.insert(label, new_field); + } + + Record( + new_fields, + instantiate_rigids_help(subs, max_rank, pools, ext_var), + ) + } + + TagUnion(tags, ext_var) => { + let mut new_tags = MutMap::default(); + + for (tag, vars) in tags { + let new_vars: Vec = vars + .into_iter() + .map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) + .collect(); + new_tags.insert(tag, new_vars); + } + + TagUnion( + new_tags, + instantiate_rigids_help(subs, max_rank, pools, ext_var), + ) + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut new_tags = MutMap::default(); + + let new_rec_var = instantiate_rigids_help(subs, max_rank, pools, rec_var); + + for (tag, vars) in tags { + let new_vars: Vec = vars + .into_iter() + .map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) + .collect(); + new_tags.insert(tag, new_vars); + } + + RecursiveTagUnion( + new_rec_var, + new_tags, + instantiate_rigids_help(subs, max_rank, pools, ext_var), + ) + } + }; + + subs.set(copy, make_descriptor(Structure(new_flat_type))); + + copy + } + + FlexVar(_) | Error => copy, + + RecursionVar { + opt_name, + structure, + } => { + let new_structure = instantiate_rigids_help(subs, max_rank, pools, structure); + + subs.set( + copy, + make_descriptor(RecursionVar { + opt_name, + structure: new_structure, + }), + ); + + copy + } + + RigidVar(name) => { + subs.set(copy, make_descriptor(FlexVar(Some(name)))); + + copy + } + + Alias(symbol, args, real_type_var) => { + let new_args = args + .into_iter() + .map(|(name, var)| (name, instantiate_rigids_help(subs, max_rank, pools, var))) + .collect(); + let new_real_type_var = instantiate_rigids_help(subs, max_rank, pools, real_type_var); + let new_content = Alias(symbol, new_args, new_real_type_var); + + subs.set(copy, make_descriptor(new_content)); + + copy + } + } +} + +fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { + let copy = deep_copy_var_help(subs, rank, pools, var); + + subs.restore(var); + + copy +} + +fn deep_copy_var_help( + subs: &mut Subs, + max_rank: Rank, + pools: &mut Pools, + var: Variable, +) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + let desc = subs.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + return copy; + } else if desc.rank != Rank::NONE { + return var; + } + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let content = desc.content; + let copy = subs.fresh(make_descriptor(content.clone())); + + pools.get_mut(max_rank).push(copy); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + subs.set( + var, + Descriptor { + content: content.clone(), + rank: desc.rank, + mark: Mark::NONE, + copy: copy.into(), + }, + ); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match content { + Structure(flat_type) => { + let new_flat_type = match flat_type { + Apply(symbol, args) => { + let args = args + .into_iter() + .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) + .collect(); + + Apply(symbol, args) + } + + Func(arg_vars, closure_var, ret_var) => { + let new_ret_var = deep_copy_var_help(subs, max_rank, pools, ret_var); + let new_closure_var = deep_copy_var_help(subs, max_rank, pools, closure_var); + let arg_vars = arg_vars + .into_iter() + .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) + .collect(); + + Func(arg_vars, new_closure_var, new_ret_var) + } + + same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, + + Record(fields, ext_var) => { + let mut new_fields = MutMap::default(); + + for (label, field) in fields { + use RecordField::*; + + let new_field = match field { + Demanded(var) => { + Demanded(deep_copy_var_help(subs, max_rank, pools, var)) + } + Required(var) => { + Required(deep_copy_var_help(subs, max_rank, pools, var)) + } + Optional(var) => { + Optional(deep_copy_var_help(subs, max_rank, pools, var)) + } + }; + + new_fields.insert(label, new_field); + } + + Record( + new_fields, + deep_copy_var_help(subs, max_rank, pools, ext_var), + ) + } + + TagUnion(tags, ext_var) => { + let mut new_tags = MutMap::default(); + + for (tag, vars) in tags { + let new_vars: Vec = vars + .into_iter() + .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) + .collect(); + new_tags.insert(tag, new_vars); + } + + TagUnion(new_tags, deep_copy_var_help(subs, max_rank, pools, ext_var)) + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut new_tags = MutMap::default(); + + let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); + + for (tag, vars) in tags { + let new_vars: Vec = vars + .into_iter() + .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) + .collect(); + new_tags.insert(tag, new_vars); + } + + RecursiveTagUnion( + new_rec_var, + new_tags, + deep_copy_var_help(subs, max_rank, pools, ext_var), + ) + } + }; + + subs.set(copy, make_descriptor(Structure(new_flat_type))); + + copy + } + + FlexVar(_) | Error => copy, + + RecursionVar { + opt_name, + structure, + } => { + let new_structure = deep_copy_var_help(subs, max_rank, pools, structure); + + subs.set( + copy, + make_descriptor(RecursionVar { + opt_name, + structure: new_structure, + }), + ); + + copy + } + + RigidVar(name) => { + subs.set(copy, make_descriptor(FlexVar(Some(name)))); + + copy + } + + Alias(symbol, args, real_type_var) => { + let new_args = args + .into_iter() + .map(|(name, var)| (name, deep_copy_var_help(subs, max_rank, pools, var))) + .collect(); + let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); + let new_content = Alias(symbol, new_args, new_real_type_var); + + subs.set(copy, make_descriptor(new_content)); + + copy + } + } +} + +fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { + let var = subs.fresh(Descriptor { + content, + rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }); + + pools.get_mut(rank).push(var); + + var +} diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs index 28cc287528..0a769993f3 100644 --- a/editor/tests/solve_expr2.rs +++ b/editor/tests/solve_expr2.rs @@ -5,16 +5,22 @@ extern crate indoc; use bumpalo::Bump; use roc_can::expected::Expected; +use roc_collections::all::MutMap; +use roc_editor::lang::solve; use roc_editor::lang::{ constrain::constrain_expr, + constrain::Constraint, expr::{str_to_expr2, Env}, pool::Pool, scope::Scope, types::Type2, }; +use roc_module::ident::{Lowercase, TagName}; +use roc_module::symbol::Symbol; use roc_module::symbol::{IdentIds, ModuleIds}; use roc_region::all::Region; -use roc_solve::module::run_solve; +use roc_types::solved_types::Solved; +use roc_types::subs::{Subs, Variable}; use roc_types::{pretty_print::content_to_string, subs::VarStore, types::Type}; fn ed_constraint_to_can_constraint( @@ -46,6 +52,33 @@ fn type2_to_type(typ: &Type2) -> Type { } } +fn run_solve( + aliases: MutMap, + rigid_variables: MutMap, + constraint: Constraint, + var_store: VarStore, +) -> (Solved, solve::Env, Vec) { + let env = solve::Env { + vars_by_symbol: MutMap::default(), + aliases, + }; + + let mut subs = Subs::new(var_store.into()); + + for (var, name) in rigid_variables { + subs.rigid_var(var, name); + } + + // Now that the module is parsed, canonicalized, and constrained, + // we need to type check it. + let mut problems = Vec::new(); + + // Run the solver to populate Subs. + let (solved_subs, solved_env) = solve::run(&env, &mut problems, subs, &constraint); + + (solved_subs, solved_env, problems) +} + fn infer_eq(actual: &str, expected_str: &str) { let mut env_pool = Pool::with_capacity(1024); let env_arena = Bump::new(); @@ -83,8 +116,6 @@ fn infer_eq(actual: &str, expected_str: &str) { Expected::NoExpectation(Type2::Variable(var)), ); - let constraint = ed_constraint_to_can_constraint(constraint); - let (mut solved, _, _) = run_solve( Default::default(), Default::default(), From c55c35e16463f13da42ffa49df967cde64d7e543 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 12 Apr 2021 23:29:07 +0200 Subject: [PATCH 6/6] extend solve --- editor/src/lang/solve.rs | 106 +++++++++++++++++++++--------------- editor/tests/solve_expr2.rs | 51 ++++++----------- 2 files changed, 80 insertions(+), 77 deletions(-) diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs index e35ff0ce56..c2e3fd089c 100644 --- a/editor/src/lang/solve.rs +++ b/editor/src/lang/solve.rs @@ -1,12 +1,14 @@ +#![allow(clippy::all)] +#![allow(dead_code)] use crate::lang::constrain::Constraint::{self, *}; +use crate::lang::pool::Pool; use crate::lang::types::Type2; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{ImMap, MutMap}; +use roc_collections::all::MutMap; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::solved_types::Solved; use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; -use roc_types::types::Type; use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField}; use roc_unify::unify::unify; use roc_unify::unify::Unified::*; @@ -132,18 +134,20 @@ struct State { } pub fn run( + mempool: &mut Pool, env: &Env, problems: &mut Vec, mut subs: Subs, constraint: &Constraint, ) -> (Solved, Env) { - let env = run_in_place(env, problems, &mut subs, constraint); + let env = run_in_place(mempool, env, problems, &mut subs, constraint); (Solved(subs), env) } /// Modify an existing subs in-place instead pub fn run_in_place( + mempool: &mut Pool, env: &Env, problems: &mut Vec, subs: &mut Subs, @@ -156,6 +160,7 @@ pub fn run_in_place( }; let rank = Rank::toplevel(); let state = solve( + mempool, env, state, rank, @@ -171,7 +176,8 @@ pub fn run_in_place( #[allow(clippy::too_many_arguments)] fn solve( - env: &Env, + mempool: &mut Pool, + _env: &Env, state: State, rank: Rank, pools: &mut Pools, @@ -191,8 +197,9 @@ fn solve( // copy // } Eq(typ, expectation, category, region) => { - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + let actual = type_to_var(mempool, subs, rank, pools, cached_aliases, typ); let expected = type_to_var( + mempool, subs, rank, pools, @@ -613,26 +620,28 @@ fn solve( } fn type_to_var( + mempool: &mut Pool, subs: &mut Subs, rank: Rank, pools: &mut Pools, cached: &mut MutMap, typ: &Type2, ) -> Variable { - type_to_variable(subs, rank, pools, cached, typ) + type_to_variable(mempool, subs, rank, pools, cached, typ) } /// Abusing existing functions for our purposes /// this is to put a solved type back into subs -pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type2) -> Variable { +pub fn insert_type_into_subs(mempool: &mut Pool, subs: &mut Subs, typ: &Type2) -> Variable { let rank = Rank::NONE; let mut pools = Pools::default(); let mut cached = MutMap::default(); - type_to_variable(subs, rank, &mut pools, &mut cached, typ) + type_to_variable(mempool, subs, rank, &mut pools, &mut cached, typ) } fn type_to_variable( + mempool: &mut Pool, subs: &mut Subs, rank: Rank, pools: &mut Pools, @@ -645,10 +654,10 @@ fn type_to_variable( Variable(var) => *var, Apply(symbol, args) => { let mut arg_vars = Vec::with_capacity(args.len()); - - // for arg in args { - // arg_vars.push(type_to_variable(subs, rank, pools, cached, arg)) - // } + for var_id in args.iter_node_ids() { + let arg = mempool.get(var_id); + arg_vars.push(type_to_variable(mempool, subs, rank, pools, cached, arg)) + } let flat_type = FlatType::Apply(*symbol, arg_vars); let content = Content::Structure(flat_type); @@ -656,9 +665,49 @@ fn type_to_variable( register(subs, rank, pools, content) } + EmptyRec => roc_types::subs::Variable::EMPTY_RECORD, + EmptyTagUnion => roc_types::subs::Variable::EMPTY_TAG_UNION, + + Record(fields, ext_id) => { + let mut field_vars = MutMap::default(); + + for node_id in fields.iter_node_ids() { + use RecordField::*; + + let (field, field_type) = mempool.get(node_id); + + let field_var = match field_type { + Required(typ) => { + Required(type_to_variable(mempool, subs, rank, pools, cached, typ)) + } + Optional(typ) => { + Optional(type_to_variable(mempool, subs, rank, pools, cached, typ)) + } + Demanded(typ) => { + Demanded(type_to_variable(mempool, subs, rank, pools, cached, typ)) + } + }; + + field_vars.insert(field.as_str(mempool).into(), field_var); + } + + let ext = mempool.get(*ext_id); + let temp_ext_var = type_to_variable(mempool, subs, rank, pools, cached, ext); + let new_ext_var = match roc_types::pretty_print::chase_ext_record( + subs, + temp_ext_var, + &mut field_vars, + ) { + Ok(()) => roc_types::subs::Variable::EMPTY_RECORD, + Err((new, _)) => new, + }; + + let content = Content::Structure(FlatType::Record(field_vars, new_ext_var)); + + register(subs, rank, pools, content) + } + other => todo!("not implemented {:?}", &other), - // EmptyRec => Variable::EMPTY_RECORD, - // EmptyTagUnion => Variable::EMPTY_TAG_UNION, // // // This case is important for the rank of boolean variables // Function(args, closure_type, ret_type) => { @@ -674,35 +723,6 @@ fn type_to_variable( // // register(subs, rank, pools, content) // } - // Record(fields, ext) => { - // let mut field_vars = MutMap::default(); - // - // for (field, field_type) in fields { - // use RecordField::*; - // - // let field_var = match field_type { - // Required(typ) => Required(type_to_variable(subs, rank, pools, cached, typ)), - // Optional(typ) => Optional(type_to_variable(subs, rank, pools, cached, typ)), - // Demanded(typ) => Demanded(type_to_variable(subs, rank, pools, cached, typ)), - // }; - // - // field_vars.insert(field.clone(), field_var); - // } - // - // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - // let new_ext_var = match roc_types::pretty_print::chase_ext_record( - // subs, - // temp_ext_var, - // &mut field_vars, - // ) { - // Ok(()) => Variable::EMPTY_RECORD, - // Err((new, _)) => new, - // }; - // - // let content = Content::Structure(FlatType::Record(field_vars, new_ext_var)); - // - // register(subs, rank, pools, content) - // } // TagUnion(tags, ext) => { // let mut tag_vars = MutMap::default(); // diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs index 0a769993f3..51bc7267fc 100644 --- a/editor/tests/solve_expr2.rs +++ b/editor/tests/solve_expr2.rs @@ -15,44 +15,16 @@ use roc_editor::lang::{ scope::Scope, types::Type2, }; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_module::symbol::{IdentIds, ModuleIds}; use roc_region::all::Region; use roc_types::solved_types::Solved; use roc_types::subs::{Subs, Variable}; -use roc_types::{pretty_print::content_to_string, subs::VarStore, types::Type}; - -fn ed_constraint_to_can_constraint( - constraint: roc_editor::lang::constrain::Constraint, -) -> roc_can::constraint::Constraint { - match constraint { - roc_editor::lang::constrain::Constraint::Eq(typ, expected, category, region) => { - let new_typ = type2_to_type(&typ); - let expected_typ = expected.get_type_ref(); - - let expected_typ = type2_to_type(expected_typ); - - roc_can::constraint::Constraint::Eq( - new_typ, - expected.replace(expected_typ), - category, - region, - ) - } - _ => todo!("{:?}", constraint), - } -} - -fn type2_to_type(typ: &Type2) -> Type { - match typ { - Type2::Apply(symbol, _) => Type::Apply(*symbol, Vec::new()), - Type2::Variable(var) => Type::Variable(*var), - _ => todo!("{:?}", typ), - } -} +use roc_types::{pretty_print::content_to_string, subs::VarStore}; fn run_solve( + mempool: &mut Pool, aliases: MutMap, rigid_variables: MutMap, constraint: Constraint, @@ -74,7 +46,7 @@ fn run_solve( let mut problems = Vec::new(); // Run the solver to populate Subs. - let (solved_subs, solved_env) = solve::run(&env, &mut problems, subs, &constraint); + let (solved_subs, solved_env) = solve::run(mempool, &env, &mut problems, subs, &constraint); (solved_subs, solved_env, problems) } @@ -116,18 +88,29 @@ fn infer_eq(actual: &str, expected_str: &str) { Expected::NoExpectation(Type2::Variable(var)), ); + let Env { + pool, + var_store: ref_var_store, + .. + } = env; + + // extract the var_store out of the env again + let mut var_store = VarStore::default(); + std::mem::swap(ref_var_store, &mut var_store); + let (mut solved, _, _) = run_solve( + pool, Default::default(), Default::default(), constraint, var_store, ); - let mut subs = solved.inner_mut(); + let subs = solved.inner_mut(); let content = subs.get(var).content; - let actual_str = content_to_string(content, &mut subs, mod_id, &Default::default()); + let actual_str = content_to_string(content, &subs, mod_id, &Default::default()); assert_eq!(actual_str, expected_str); }