diff --git a/src/canonicalize.rs b/src/canonicalize.rs index 7e9162ebd8..29ca3ad9cc 100644 --- a/src/canonicalize.rs +++ b/src/canonicalize.rs @@ -23,7 +23,7 @@ pub enum Expr { // Lookups Var(Symbol), - InterpolatedStr(Vec<(String, Expr)>, String), + InterpolatedStr(Vec<(String, Located)>, String), // Pattern Matching Case(Box>, Vec<(Located, Located)>), @@ -431,7 +431,7 @@ fn canonicalize( expr::Expr::InterpolatedStr(pairs, suffix) => { let mut output = Output::new(); - let can_pairs: Vec<(String, Expr)> = pairs.into_iter().map(|(string, loc_ident)| { + let can_pairs: Vec<(String, Located)> = pairs.into_iter().map(|(string, loc_ident)| { // From a language design perspective, we only permit idents in interpolation. // However, in a canonical Expr we store it as a full Expr, not a Symbol. // This is so that we can resolve it to either Var or Unrecognized; if we @@ -440,7 +440,7 @@ fn canonicalize( match resolve_ident(&env, &scope, loc_ident.value, &mut output.references) { Ok(symbol) => Var(symbol), Err(ident) => { - let loc_ident = Located {region: loc_ident.region, value: ident}; + let loc_ident = Located {region: loc_ident.region.clone(), value: ident}; env.problem(Problem::UnrecognizedConstant(loc_ident.clone())); @@ -448,7 +448,7 @@ fn canonicalize( } }; - (string, can_expr) + (string, Located { region: loc_ident.region, value: can_expr }) }).collect(); (InterpolatedStr(can_pairs, suffix), output) diff --git a/src/constrain.rs b/src/constrain.rs index 9bbe66af37..94d1ed832f 100644 --- a/src/constrain.rs +++ b/src/constrain.rs @@ -32,7 +32,20 @@ pub fn constrain( Approx(_) => { fractional(subs, expected, region) }, Str(_) => { Eq(string(), expected, region) }, EmptyStr => { Eq(string(), expected, region) }, - InterpolatedStr(_, _) => { Eq(string(), expected, region) }, + InterpolatedStr(pairs, _) => { + let mut constraints = Vec::with_capacity(pairs.len() + 1); + + for (_, loc_interpolated_expr) in pairs { + let expected_str = ForReason(Reason::InterpolatedStringVar, string(), loc_interpolated_expr.region.clone()); + let constraint = constrain(bound_vars, subs, loc_interpolated_expr, expected_str); + + constraints.push(constraint); + } + + constraints.push(Eq(string(), expected, region)); + + And(constraints) + }, EmptyRecord => { Eq(EmptyRec, expected, region) }, EmptyList => { Eq(empty_list(subs.mk_flex_var()), expected, region) }, List(elems) => { list(elems, bound_vars, subs, expected, region) }, diff --git a/src/types.rs b/src/types.rs index 9ccc78f478..c42688b9ab 100644 --- a/src/types.rs +++ b/src/types.rs @@ -39,6 +39,7 @@ pub enum Reason { OperatorLeftArg(Operator), OperatorRightArg(Operator), FractionalLiteral, + InterpolatedStringVar, ElemInList, } diff --git a/tests/test_infer.rs b/tests/test_infer.rs index 2d05a813f8..80473b9d56 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -216,6 +216,21 @@ mod test_infer { ); } + // INTERPOLATED STRING + + #[test] + fn infer_interpolated_string() { + infer_eq( + indoc!(r#" + whatItIs = "great" + + "type inference is \(whatItIs)!" + "#), + "String.String" + ); + } + + // LIST MISMATCH #[test] @@ -343,17 +358,6 @@ mod test_infer { } - // #[test] - // fn infer_interpolated_string() { - // infer_eq( - // indoc!(r#" - // whatItIs = "great" - - // "type inference is \(whatItIs)!" - // "#), - // "String.String" - // ); - // } // #[test] // fn int_thunk() {