mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Use bidirectional annotation checking
This commit is contained in:
parent
4242bb621c
commit
dbc44e0236
13 changed files with 383 additions and 74 deletions
|
|
@ -692,6 +692,11 @@ pub fn checkPattern(self: *Self, pattern_idx: CIR.Pattern.Idx) std.mem.Allocator
|
|||
|
||||
/// Check the types for an exprexpression. Returns whether evaluating the expr might perform side effects.
|
||||
pub fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx) std.mem.Allocator.Error!bool {
|
||||
return self.checkExprWithExpected(expr_idx, null);
|
||||
}
|
||||
|
||||
/// Check expression with an optional expected type for bidirectional type checking
|
||||
pub fn checkExprWithExpected(self: *Self, expr_idx: CIR.Expr.Idx, expected_type: ?Var) std.mem.Allocator.Error!bool {
|
||||
const trace = tracy.trace(@src());
|
||||
defer trace.end();
|
||||
|
||||
|
|
@ -705,6 +710,12 @@ pub fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx) std.mem.Allocator.Error!bo
|
|||
// constraints will be checked when unified with expected types.
|
||||
// The type variable for this expression was already created with the
|
||||
// appropriate num_unbound or int_unbound content during canonicalization.
|
||||
|
||||
// If we have an expected type, unify immediately to constrain the literal
|
||||
if (expected_type) |expected| {
|
||||
const literal_var = @as(Var, @enumFromInt(@intFromEnum(expr_idx)));
|
||||
_ = try self.unify(literal_var, expected);
|
||||
}
|
||||
},
|
||||
.e_frac_f32 => |_| {
|
||||
// Fractional literals have their type constraints (fits_in_f32, fits_in_dec)
|
||||
|
|
@ -1059,7 +1070,7 @@ pub fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx) std.mem.Allocator.Error!bo
|
|||
_ = try self.unify(closure_var, lambda_var);
|
||||
},
|
||||
.e_lambda => |lambda| {
|
||||
does_fx = try self.checkLambdaWithAnno(expr_idx, expr_region, lambda, null);
|
||||
does_fx = try self.checkLambdaWithAnno(expr_idx, expr_region, lambda, expected_type);
|
||||
},
|
||||
.e_tuple => |tuple| {
|
||||
// Check tuple elements
|
||||
|
|
@ -1356,8 +1367,9 @@ fn checkLambdaWithAnno(
|
|||
|
||||
var mb_expected_var: ?Var = null;
|
||||
var mb_expected_func: ?types_mod.Func = null;
|
||||
var expected_return_type: ?Var = null;
|
||||
|
||||
// STEP 1: Apply annotation constraints to parameters FIRST
|
||||
// STEP 1: Extract expected function type and argument/return types
|
||||
if (anno_type) |anno_var| {
|
||||
mb_expected_var = anno_var;
|
||||
|
||||
|
|
@ -1372,6 +1384,8 @@ fn checkLambdaWithAnno(
|
|||
|
||||
if (mb_expected_func) |func| {
|
||||
const expected_args = self.types.sliceVars(func.args);
|
||||
expected_return_type = func.ret;
|
||||
|
||||
if (expected_args.len == arg_patterns.len) {
|
||||
// Constrain parameters with annotation types
|
||||
for (arg_patterns, expected_args) |pattern_idx, expected_arg| {
|
||||
|
|
@ -1384,41 +1398,24 @@ fn checkLambdaWithAnno(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 2: Check the body - but first set up return type constraints if needed
|
||||
const return_var = @as(Var, @enumFromInt(@intFromEnum(lambda.body)));
|
||||
|
||||
// For better constraint propagation, especially for numeric literals in lambda bodies,
|
||||
// we want to establish the return type constraint before checking the body.
|
||||
// However, to avoid breaking error reporting in other cases, we only do this
|
||||
// when the expected return type is a concrete base type (not a type variable, nominal type, etc.)
|
||||
var early_unify = false;
|
||||
if (mb_expected_func) |func| {
|
||||
const ret_resolved = self.types.resolveVar(func.ret);
|
||||
if (ret_resolved.desc.content == .structure) {
|
||||
// Only do early unification for concrete base types (num, str, etc.)
|
||||
// Skip nominal types, tag unions, and other complex types
|
||||
const should_early_unify = switch (ret_resolved.desc.content.structure) {
|
||||
.num, .str => true,
|
||||
.nominal_type, .tag_union, .fn_pure, .fn_effectful, .fn_unbound => false,
|
||||
.list, .box, .tuple, .record, .record_unbound, .record_poly => false,
|
||||
.empty_record, .empty_tag_union, .list_unbound => false,
|
||||
};
|
||||
|
||||
if (should_early_unify) {
|
||||
// The return type is a concrete base type, unify early for better constraint propagation
|
||||
_ = try self.unify(return_var, func.ret);
|
||||
early_unify = true;
|
||||
}
|
||||
} else {
|
||||
// No annotation - just check patterns normally
|
||||
for (arg_patterns) |pattern_idx| {
|
||||
try self.checkPattern(pattern_idx);
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 3: Check the body
|
||||
const is_effectful = try self.checkExpr(lambda.body);
|
||||
// STEP 2: Check the lambda body with expected return type
|
||||
// This is the key improvement - we pass the expected return type down
|
||||
// so that numeric literals and nested expressions get properly constrained
|
||||
const is_effectful = if (expected_return_type) |expected_ret|
|
||||
try self.checkExprWithExpected(lambda.body, expected_ret)
|
||||
else
|
||||
try self.checkExpr(lambda.body);
|
||||
|
||||
// STEP 4: Build the function type
|
||||
// STEP 3: Build the function type
|
||||
const fn_var = ModuleEnv.varFrom(expr_idx);
|
||||
const return_var = @as(Var, @enumFromInt(@intFromEnum(lambda.body)));
|
||||
|
||||
if (is_effectful) {
|
||||
_ = try self.types.setVarContent(fn_var, try self.types.mkFuncEffectful(arg_vars, return_var));
|
||||
|
|
@ -1426,14 +1423,12 @@ fn checkLambdaWithAnno(
|
|||
_ = try self.types.setVarContent(fn_var, try self.types.mkFuncUnbound(arg_vars, return_var));
|
||||
}
|
||||
|
||||
// STEP 5: Validate the function body against the annotation return type (if not done already)
|
||||
if (mb_expected_func) |func| {
|
||||
if (!early_unify) {
|
||||
_ = try self.unify(return_var, func.ret);
|
||||
}
|
||||
// STEP 4: Validate the function body against the annotation return type
|
||||
if (expected_return_type) |expected_ret| {
|
||||
_ = try self.unify(return_var, expected_ret);
|
||||
}
|
||||
|
||||
// STEP 6: Validate the entire function against the annotation
|
||||
// STEP 5: Validate the entire function against the annotation
|
||||
if (mb_expected_var) |expected_var| {
|
||||
_ = try self.unify(fn_var, expected_var);
|
||||
}
|
||||
|
|
@ -1448,35 +1443,40 @@ fn checkBinopExpr(self: *Self, expr_idx: CIR.Expr.Idx, expr_region: Region, bino
|
|||
const trace = tracy.trace(@src());
|
||||
defer trace.end();
|
||||
|
||||
var does_fx = try self.checkExpr(binop.lhs);
|
||||
does_fx = try self.checkExpr(binop.rhs) or does_fx;
|
||||
|
||||
switch (binop.op) {
|
||||
.add, .sub, .mul, .div, .rem, .pow, .div_trunc => {
|
||||
// For now, we'll constrain both operands to be numbers
|
||||
// In the future, this will use static dispatch based on the lhs type
|
||||
const lhs_var = @as(Var, @enumFromInt(@intFromEnum(binop.lhs)));
|
||||
const rhs_var = @as(Var, @enumFromInt(@intFromEnum(binop.rhs)));
|
||||
// For arithmetic operations, create a shared number type for operands and result
|
||||
const result_var = @as(Var, @enumFromInt(@intFromEnum(expr_idx)));
|
||||
|
||||
// Create a fresh number variable for the operation
|
||||
// Create a fresh number variable that all operands should match
|
||||
const num_content = Content{ .structure = .{ .num = .{ .num_unbound = .{ .sign_needed = false, .bits_needed = 0 } } } };
|
||||
const num_var_lhs = try self.freshFromContent(num_content, expr_region);
|
||||
const num_var_rhs = try self.freshFromContent(num_content, expr_region);
|
||||
const num_var_result = try self.freshFromContent(num_content, expr_region);
|
||||
const shared_num_var = try self.freshFromContent(num_content, expr_region);
|
||||
|
||||
// Unify lhs, rhs, and result with the number type
|
||||
_ = try self.unify(num_var_lhs, lhs_var);
|
||||
_ = try self.unify(num_var_rhs, rhs_var);
|
||||
_ = try self.unify(result_var, num_var_result);
|
||||
// Check operands with the shared numeric type as expected
|
||||
var does_fx = try self.checkExprWithExpected(binop.lhs, shared_num_var);
|
||||
does_fx = try self.checkExprWithExpected(binop.rhs, shared_num_var) or does_fx;
|
||||
|
||||
// Result should also be of the same numeric type
|
||||
_ = try self.unify(result_var, shared_num_var);
|
||||
|
||||
return does_fx;
|
||||
},
|
||||
.lt, .gt, .le, .ge, .eq, .ne => {
|
||||
// Check operands first
|
||||
var does_fx = try self.checkExpr(binop.lhs);
|
||||
does_fx = try self.checkExpr(binop.rhs) or does_fx;
|
||||
|
||||
// Comparison operators always return Bool
|
||||
const expr_var = @as(Var, @enumFromInt(@intFromEnum(expr_idx)));
|
||||
const fresh_bool = try self.instantiateVarAnon(ModuleEnv.varFrom(can.Can.BUILTIN_BOOL_TYPE), .{ .explicit = expr_region });
|
||||
_ = try self.unify(expr_var, fresh_bool);
|
||||
|
||||
return does_fx;
|
||||
},
|
||||
.@"and" => {
|
||||
var does_fx = try self.checkExpr(binop.lhs);
|
||||
does_fx = try self.checkExpr(binop.rhs) or does_fx;
|
||||
|
||||
const lhs_fresh_bool = try self.instantiateVarAnon(ModuleEnv.varFrom(can.Can.BUILTIN_BOOL_TYPE), .{ .explicit = expr_region });
|
||||
const lhs_result = try self.unify(lhs_fresh_bool, @enumFromInt(@intFromEnum(binop.lhs)));
|
||||
self.setDetailIfTypeMismatch(lhs_result, .{ .invalid_bool_binop = .{
|
||||
|
|
@ -1494,8 +1494,13 @@ fn checkBinopExpr(self: *Self, expr_idx: CIR.Expr.Idx, expr_region: Region, bino
|
|||
.binop = .@"and",
|
||||
} });
|
||||
}
|
||||
|
||||
return does_fx;
|
||||
},
|
||||
.@"or" => {
|
||||
var does_fx = try self.checkExpr(binop.lhs);
|
||||
does_fx = try self.checkExpr(binop.rhs) or does_fx;
|
||||
|
||||
const lhs_fresh_bool = try self.instantiateVarAnon(ModuleEnv.varFrom(can.Can.BUILTIN_BOOL_TYPE), .{ .explicit = expr_region });
|
||||
const lhs_result = try self.unify(lhs_fresh_bool, @enumFromInt(@intFromEnum(binop.lhs)));
|
||||
self.setDetailIfTypeMismatch(lhs_result, .{ .invalid_bool_binop = .{
|
||||
|
|
@ -1513,12 +1518,20 @@ fn checkBinopExpr(self: *Self, expr_idx: CIR.Expr.Idx, expr_region: Region, bino
|
|||
.binop = .@"or",
|
||||
} });
|
||||
}
|
||||
},
|
||||
.pipe_forward => {},
|
||||
.null_coalesce => {},
|
||||
}
|
||||
|
||||
return does_fx;
|
||||
return does_fx;
|
||||
},
|
||||
.pipe_forward => {
|
||||
var does_fx = try self.checkExpr(binop.lhs);
|
||||
does_fx = try self.checkExpr(binop.rhs) or does_fx;
|
||||
return does_fx;
|
||||
},
|
||||
.null_coalesce => {
|
||||
var does_fx = try self.checkExpr(binop.lhs);
|
||||
does_fx = try self.checkExpr(binop.rhs) or does_fx;
|
||||
return does_fx;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn checkUnaryMinusExpr(self: *Self, expr_idx: CIR.Expr.Idx, expr_region: Region, unary: CIR.Expr.UnaryMinus) Allocator.Error!bool {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ NO CHANGE
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt @4.1-4.9 (type "Num(_size) -> Num(_size2)")))
|
||||
(patt @4.1-4.9 (type "_arg -> Num(_size)")))
|
||||
(expressions
|
||||
(expr @4.12-10.2 (type "Num(_size) -> Num(_size2)"))))
|
||||
(expr @4.12-10.2 (type "_arg -> Num(_size)"))))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -40,5 +40,5 @@ NO CHANGE
|
|||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(expr @1.1-1.10 (type "Num(_size) -> Num(_size2)"))
|
||||
(expr @1.1-1.10 (type "_arg -> Num(_size)"))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -43,5 +43,5 @@ NO CHANGE
|
|||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(expr @1.1-1.13 (type "Num(_size), Num(_size2) -> Num(_size3)"))
|
||||
(expr @1.1-1.13 (type "_arg, _arg2 -> Num(_size)"))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -166,5 +166,5 @@ CloseCurly(10:1-10:2),EndOfFile(10:2-10:2),
|
|||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(expr @1.1-10.2 (type "{ someTag: [Some(Num(_size))]_others, noneTag: [None]_others2, okTag: Result(Str, err), errTag: Result(ok, Str), addOne: Num(_size2) -> Num(_size3), result: _field, nested: [Some(Error)]_others3, tagList: List([Some(Num(_size4))][None]_others4) }"))
|
||||
(expr @1.1-10.2 (type "{ someTag: [Some(Num(_size))]_others, noneTag: [None]_others2, okTag: Result(Str, err), errTag: Result(ok, Str), addOne: _arg -> Num(_size2), result: _field, nested: [Some(Error)]_others3, tagList: List([Some(Num(_size3))][None]_others4) }"))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -153,13 +153,13 @@ NO CHANGE
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt @6.1-6.9 (type "Num(_size), Num(_size2) -> Num(_size3)"))
|
||||
(patt @6.1-6.9 (type "_arg, _arg2 -> Num(_size)"))
|
||||
(patt @9.1-9.14 (type "_arg -> _ret"))
|
||||
(patt @12.1-12.9 (type "Num(_size) -> _ret"))
|
||||
(patt @12.1-12.9 (type "_arg -> _ret"))
|
||||
(patt @14.1-14.6 (type "_a")))
|
||||
(expressions
|
||||
(expr @6.12-6.24 (type "Num(_size), Num(_size2) -> Num(_size3)"))
|
||||
(expr @6.12-6.24 (type "_arg, _arg2 -> Num(_size)"))
|
||||
(expr @9.17-9.36 (type "_arg -> _ret"))
|
||||
(expr @12.12-12.45 (type "Num(_size) -> _ret"))
|
||||
(expr @12.12-12.45 (type "_arg -> _ret"))
|
||||
(expr @14.9-14.21 (type "_a"))))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -72,5 +72,5 @@ NO CHANGE
|
|||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(expr @1.1-1.14 (type "Num(_size) -> _arg -> Num(_size2)"))
|
||||
(expr @1.1-1.14 (type "_arg -> _arg2 -> Num(_size)"))
|
||||
~~~
|
||||
|
|
|
|||
295
test/snapshots/lambda_currying_constraint.md
Normal file
295
test/snapshots/lambda_currying_constraint.md
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
# META
|
||||
~~~ini
|
||||
description=Lambda currying with polymorphic function constraints - tests if numeric literals in curried functions get properly constrained
|
||||
type=file
|
||||
~~~
|
||||
# SOURCE
|
||||
~~~roc
|
||||
module [makeAdder, curriedAdd, applyTwice]
|
||||
|
||||
# Function that returns a function with polymorphic type
|
||||
makeAdder : a -> (a -> a)
|
||||
makeAdder = |x| |y| x + y
|
||||
|
||||
# Should constrain the literal 5 to I64
|
||||
curriedAdd : I64 -> I64
|
||||
curriedAdd = makeAdder(5)
|
||||
|
||||
# Higher-order function that applies a function twice
|
||||
applyTwice : (a -> a), a -> a
|
||||
applyTwice = |f, x| f(f(x))
|
||||
|
||||
# Should constrain the literal 3 to I64
|
||||
addThreeTwice : I64 -> I64
|
||||
addThreeTwice = |n| applyTwice(|x| x + 3, n)
|
||||
~~~
|
||||
# EXPECTED
|
||||
PARSE ERROR - lambda_currying_constraint.md:12:22:12:23
|
||||
PARSE ERROR - lambda_currying_constraint.md:12:24:12:25
|
||||
PARSE ERROR - lambda_currying_constraint.md:12:26:12:28
|
||||
PARSE ERROR - lambda_currying_constraint.md:12:29:12:30
|
||||
TYPE MISMATCH - lambda_currying_constraint.md:4:18:4:26
|
||||
# PROBLEMS
|
||||
**PARSE ERROR**
|
||||
A parsing error occurred: `statement_unexpected_token`
|
||||
This is an unexpected parsing error. Please check your syntax.
|
||||
|
||||
Here is the problematic code:
|
||||
**lambda_currying_constraint.md:12:22:12:23:**
|
||||
```roc
|
||||
applyTwice : (a -> a), a -> a
|
||||
```
|
||||
^
|
||||
|
||||
|
||||
**PARSE ERROR**
|
||||
A parsing error occurred: `statement_unexpected_token`
|
||||
This is an unexpected parsing error. Please check your syntax.
|
||||
|
||||
Here is the problematic code:
|
||||
**lambda_currying_constraint.md:12:24:12:25:**
|
||||
```roc
|
||||
applyTwice : (a -> a), a -> a
|
||||
```
|
||||
^
|
||||
|
||||
|
||||
**PARSE ERROR**
|
||||
A parsing error occurred: `statement_unexpected_token`
|
||||
This is an unexpected parsing error. Please check your syntax.
|
||||
|
||||
Here is the problematic code:
|
||||
**lambda_currying_constraint.md:12:26:12:28:**
|
||||
```roc
|
||||
applyTwice : (a -> a), a -> a
|
||||
```
|
||||
^^
|
||||
|
||||
|
||||
**PARSE ERROR**
|
||||
A parsing error occurred: `statement_unexpected_token`
|
||||
This is an unexpected parsing error. Please check your syntax.
|
||||
|
||||
Here is the problematic code:
|
||||
**lambda_currying_constraint.md:12:29:12:30:**
|
||||
```roc
|
||||
applyTwice : (a -> a), a -> a
|
||||
```
|
||||
^
|
||||
|
||||
|
||||
**TYPE MISMATCH**
|
||||
This expression is used in an unexpected way:
|
||||
**lambda_currying_constraint.md:4:18:4:26:**
|
||||
```roc
|
||||
makeAdder : a -> (a -> a)
|
||||
```
|
||||
^^^^^^^^
|
||||
|
||||
It is of type:
|
||||
_a -> a_
|
||||
|
||||
But you are trying to use it as:
|
||||
_a -> Num(_size)_
|
||||
|
||||
# TOKENS
|
||||
~~~zig
|
||||
KwModule(1:1-1:7),OpenSquare(1:8-1:9),LowerIdent(1:9-1:18),Comma(1:18-1:19),LowerIdent(1:20-1:30),Comma(1:30-1:31),LowerIdent(1:32-1:42),CloseSquare(1:42-1:43),
|
||||
LowerIdent(4:1-4:10),OpColon(4:11-4:12),LowerIdent(4:13-4:14),OpArrow(4:15-4:17),OpenRound(4:18-4:19),LowerIdent(4:19-4:20),OpArrow(4:21-4:23),LowerIdent(4:24-4:25),CloseRound(4:25-4:26),
|
||||
LowerIdent(5:1-5:10),OpAssign(5:11-5:12),OpBar(5:13-5:14),LowerIdent(5:14-5:15),OpBar(5:15-5:16),OpBar(5:17-5:18),LowerIdent(5:18-5:19),OpBar(5:19-5:20),LowerIdent(5:21-5:22),OpPlus(5:23-5:24),LowerIdent(5:25-5:26),
|
||||
LowerIdent(8:1-8:11),OpColon(8:12-8:13),UpperIdent(8:14-8:17),OpArrow(8:18-8:20),UpperIdent(8:21-8:24),
|
||||
LowerIdent(9:1-9:11),OpAssign(9:12-9:13),LowerIdent(9:14-9:23),NoSpaceOpenRound(9:23-9:24),Int(9:24-9:25),CloseRound(9:25-9:26),
|
||||
LowerIdent(12:1-12:11),OpColon(12:12-12:13),OpenRound(12:14-12:15),LowerIdent(12:15-12:16),OpArrow(12:17-12:19),LowerIdent(12:20-12:21),CloseRound(12:21-12:22),Comma(12:22-12:23),LowerIdent(12:24-12:25),OpArrow(12:26-12:28),LowerIdent(12:29-12:30),
|
||||
LowerIdent(13:1-13:11),OpAssign(13:12-13:13),OpBar(13:14-13:15),LowerIdent(13:15-13:16),Comma(13:16-13:17),LowerIdent(13:18-13:19),OpBar(13:19-13:20),LowerIdent(13:21-13:22),NoSpaceOpenRound(13:22-13:23),LowerIdent(13:23-13:24),NoSpaceOpenRound(13:24-13:25),LowerIdent(13:25-13:26),CloseRound(13:26-13:27),CloseRound(13:27-13:28),
|
||||
LowerIdent(16:1-16:14),OpColon(16:15-16:16),UpperIdent(16:17-16:20),OpArrow(16:21-16:23),UpperIdent(16:24-16:27),
|
||||
LowerIdent(17:1-17:14),OpAssign(17:15-17:16),OpBar(17:17-17:18),LowerIdent(17:18-17:19),OpBar(17:19-17:20),LowerIdent(17:21-17:31),NoSpaceOpenRound(17:31-17:32),OpBar(17:32-17:33),LowerIdent(17:33-17:34),OpBar(17:34-17:35),LowerIdent(17:36-17:37),OpPlus(17:38-17:39),Int(17:40-17:41),Comma(17:41-17:42),LowerIdent(17:43-17:44),CloseRound(17:44-17:45),EndOfFile(17:45-17:45),
|
||||
~~~
|
||||
# PARSE
|
||||
~~~clojure
|
||||
(file @1.1-17.45
|
||||
(module @1.1-1.43
|
||||
(exposes @1.8-1.43
|
||||
(exposed-lower-ident @1.9-1.18
|
||||
(text "makeAdder"))
|
||||
(exposed-lower-ident @1.20-1.30
|
||||
(text "curriedAdd"))
|
||||
(exposed-lower-ident @1.32-1.42
|
||||
(text "applyTwice"))))
|
||||
(statements
|
||||
(s-type-anno @4.1-4.26 (name "makeAdder")
|
||||
(ty-fn @4.13-4.26
|
||||
(ty-var @4.13-4.14 (raw "a"))
|
||||
(ty-fn @4.19-4.25
|
||||
(ty-var @4.19-4.20 (raw "a"))
|
||||
(ty-var @4.24-4.25 (raw "a")))))
|
||||
(s-decl @5.1-5.26
|
||||
(p-ident @5.1-5.10 (raw "makeAdder"))
|
||||
(e-lambda @5.13-5.26
|
||||
(args
|
||||
(p-ident @5.14-5.15 (raw "x")))
|
||||
(e-lambda @5.17-5.26
|
||||
(args
|
||||
(p-ident @5.18-5.19 (raw "y")))
|
||||
(e-binop @5.21-5.26 (op "+")
|
||||
(e-ident @5.21-5.22 (raw "x"))
|
||||
(e-ident @5.25-5.26 (raw "y"))))))
|
||||
(s-type-anno @8.1-8.24 (name "curriedAdd")
|
||||
(ty-fn @8.14-8.24
|
||||
(ty @8.14-8.17 (name "I64"))
|
||||
(ty @8.21-8.24 (name "I64"))))
|
||||
(s-decl @9.1-9.26
|
||||
(p-ident @9.1-9.11 (raw "curriedAdd"))
|
||||
(e-apply @9.14-9.26
|
||||
(e-ident @9.14-9.23 (raw "makeAdder"))
|
||||
(e-int @9.24-9.25 (raw "5"))))
|
||||
(s-type-anno @12.1-12.22 (name "applyTwice")
|
||||
(ty-fn @12.15-12.21
|
||||
(ty-var @12.15-12.16 (raw "a"))
|
||||
(ty-var @12.20-12.21 (raw "a"))))
|
||||
(s-malformed @12.22-12.23 (tag "statement_unexpected_token"))
|
||||
(s-malformed @12.24-12.25 (tag "statement_unexpected_token"))
|
||||
(s-malformed @12.26-12.28 (tag "statement_unexpected_token"))
|
||||
(s-malformed @12.29-12.30 (tag "statement_unexpected_token"))
|
||||
(s-decl @13.1-13.28
|
||||
(p-ident @13.1-13.11 (raw "applyTwice"))
|
||||
(e-lambda @13.14-13.28
|
||||
(args
|
||||
(p-ident @13.15-13.16 (raw "f"))
|
||||
(p-ident @13.18-13.19 (raw "x")))
|
||||
(e-apply @13.21-13.28
|
||||
(e-ident @13.21-13.22 (raw "f"))
|
||||
(e-apply @13.23-13.27
|
||||
(e-ident @13.23-13.24 (raw "f"))
|
||||
(e-ident @13.25-13.26 (raw "x"))))))
|
||||
(s-type-anno @16.1-16.27 (name "addThreeTwice")
|
||||
(ty-fn @16.17-16.27
|
||||
(ty @16.17-16.20 (name "I64"))
|
||||
(ty @16.24-16.27 (name "I64"))))
|
||||
(s-decl @17.1-17.45
|
||||
(p-ident @17.1-17.14 (raw "addThreeTwice"))
|
||||
(e-lambda @17.17-17.45
|
||||
(args
|
||||
(p-ident @17.18-17.19 (raw "n")))
|
||||
(e-apply @17.21-17.45
|
||||
(e-ident @17.21-17.31 (raw "applyTwice"))
|
||||
(e-lambda @17.32-17.41
|
||||
(args
|
||||
(p-ident @17.33-17.34 (raw "x")))
|
||||
(e-binop @17.36-17.41 (op "+")
|
||||
(e-ident @17.36-17.37 (raw "x"))
|
||||
(e-int @17.40-17.41 (raw "3"))))
|
||||
(e-ident @17.43-17.44 (raw "n")))))))
|
||||
~~~
|
||||
# FORMATTED
|
||||
~~~roc
|
||||
module [makeAdder, curriedAdd, applyTwice]
|
||||
|
||||
# Function that returns a function with polymorphic type
|
||||
makeAdder : a -> (a -> a)
|
||||
makeAdder = |x| |y| x + y
|
||||
|
||||
# Should constrain the literal 5 to I64
|
||||
curriedAdd : I64 -> I64
|
||||
curriedAdd = makeAdder(5)
|
||||
|
||||
# Higher-order function that applies a function twice
|
||||
applyTwice : (a -> a)
|
||||
|
||||
applyTwice = |f, x| f(f(x))
|
||||
|
||||
# Should constrain the literal 3 to I64
|
||||
addThreeTwice : I64 -> I64
|
||||
addThreeTwice = |n| applyTwice(|x| x + 3, n)
|
||||
~~~
|
||||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(can-ir
|
||||
(d-let
|
||||
(p-assign @5.1-5.10 (ident "makeAdder"))
|
||||
(e-lambda @5.13-5.26
|
||||
(args
|
||||
(p-assign @5.14-5.15 (ident "x")))
|
||||
(e-closure @5.17-5.26
|
||||
(captures
|
||||
(capture @5.14-5.15 (ident "x")))
|
||||
(e-lambda @5.17-5.26
|
||||
(args
|
||||
(p-assign @5.18-5.19 (ident "y")))
|
||||
(e-binop @5.21-5.26 (op "add")
|
||||
(e-lookup-local @5.21-5.22
|
||||
(p-assign @5.14-5.15 (ident "x")))
|
||||
(e-lookup-local @5.25-5.26
|
||||
(p-assign @5.18-5.19 (ident "y")))))))
|
||||
(annotation @5.1-5.10
|
||||
(declared-type
|
||||
(ty-fn @4.13-4.26 (effectful false)
|
||||
(ty-var @4.13-4.14 (name "a"))
|
||||
(ty-parens @4.18-4.26
|
||||
(ty-fn @4.19-4.25 (effectful false)
|
||||
(ty-var @4.19-4.20 (name "a"))
|
||||
(ty-var @4.24-4.25 (name "a"))))))))
|
||||
(d-let
|
||||
(p-assign @9.1-9.11 (ident "curriedAdd"))
|
||||
(e-call @9.14-9.26
|
||||
(e-lookup-local @9.14-9.23
|
||||
(p-assign @5.1-5.10 (ident "makeAdder")))
|
||||
(e-int @9.24-9.25 (value "5")))
|
||||
(annotation @9.1-9.11
|
||||
(declared-type
|
||||
(ty-fn @8.14-8.24 (effectful false)
|
||||
(ty @8.14-8.17 (name "I64"))
|
||||
(ty @8.21-8.24 (name "I64"))))))
|
||||
(d-let
|
||||
(p-assign @13.1-13.11 (ident "applyTwice"))
|
||||
(e-lambda @13.14-13.28
|
||||
(args
|
||||
(p-assign @13.15-13.16 (ident "f"))
|
||||
(p-assign @13.18-13.19 (ident "x")))
|
||||
(e-call @13.21-13.28
|
||||
(e-lookup-local @13.21-13.22
|
||||
(p-assign @13.15-13.16 (ident "f")))
|
||||
(e-call @13.23-13.27
|
||||
(e-lookup-local @13.23-13.24
|
||||
(p-assign @13.15-13.16 (ident "f")))
|
||||
(e-lookup-local @13.25-13.26
|
||||
(p-assign @13.18-13.19 (ident "x")))))))
|
||||
(d-let
|
||||
(p-assign @17.1-17.14 (ident "addThreeTwice"))
|
||||
(e-closure @17.17-17.45
|
||||
(captures
|
||||
(capture @13.1-13.11 (ident "applyTwice")))
|
||||
(e-lambda @17.17-17.45
|
||||
(args
|
||||
(p-assign @17.18-17.19 (ident "n")))
|
||||
(e-call @17.21-17.45
|
||||
(e-lookup-local @17.21-17.31
|
||||
(p-assign @13.1-13.11 (ident "applyTwice")))
|
||||
(e-lambda @17.32-17.41
|
||||
(args
|
||||
(p-assign @17.33-17.34 (ident "x")))
|
||||
(e-binop @17.36-17.41 (op "add")
|
||||
(e-lookup-local @17.36-17.37
|
||||
(p-assign @17.33-17.34 (ident "x")))
|
||||
(e-int @17.40-17.41 (value "3"))))
|
||||
(e-lookup-local @17.43-17.44
|
||||
(p-assign @17.18-17.19 (ident "n"))))))
|
||||
(annotation @17.1-17.14
|
||||
(declared-type
|
||||
(ty-fn @16.17-16.27 (effectful false)
|
||||
(ty @16.17-16.20 (name "I64"))
|
||||
(ty @16.24-16.27 (name "I64")))))))
|
||||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt @5.1-5.10 (type "a -> Error"))
|
||||
(patt @9.1-9.11 (type "Error"))
|
||||
(patt @13.1-13.11 (type "_arg -> _ret, _arg2 -> _ret2"))
|
||||
(patt @17.1-17.14 (type "I64 -> I64")))
|
||||
(expressions
|
||||
(expr @5.13-5.26 (type "a -> Error"))
|
||||
(expr @9.14-9.26 (type "Error"))
|
||||
(expr @13.14-13.28 (type "_arg -> _ret, _arg2 -> _ret2"))
|
||||
(expr @17.17-17.45 (type "I64 -> I64"))))
|
||||
~~~
|
||||
|
|
@ -255,7 +255,7 @@ main = |_| {
|
|||
(patt @13.1-13.13 (type "Num(_size)"))
|
||||
(patt @16.1-16.10 (type "Num(_size)"))
|
||||
(patt @17.1-17.15 (type "Num(_size)"))
|
||||
(patt @20.1-20.7 (type "Num(_size) -> Num(_size2)"))
|
||||
(patt @20.1-20.7 (type "_arg -> Num(_size)"))
|
||||
(patt @23.1-23.12 (type "Num(_size)"))
|
||||
(patt @24.1-24.14 (type "Num(_size)"))
|
||||
(patt @26.1-26.5 (type "_arg -> Num(_size)")))
|
||||
|
|
@ -268,7 +268,7 @@ main = |_| {
|
|||
(expr @13.16-13.23 (type "Num(_size)"))
|
||||
(expr @16.13-16.23 (type "Num(_size)"))
|
||||
(expr @17.18-17.27 (type "Num(_size)"))
|
||||
(expr @20.10-20.19 (type "Num(_size) -> Num(_size2)"))
|
||||
(expr @20.10-20.19 (type "_arg -> Num(_size)"))
|
||||
(expr @23.15-23.24 (type "Num(_size)"))
|
||||
(expr @24.17-24.28 (type "Num(_size)"))
|
||||
(expr @26.8-29.2 (type "_arg -> Num(_size)"))))
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ is_named_color = |str|{
|
|||
# EXPECTED
|
||||
UNUSED VARIABLE - Color.md:30:5:30:25
|
||||
UNDEFINED VARIABLE - Color.md:68:14:68:27
|
||||
INVALID NOMINAL TAG - Color.md:23:5:23:33
|
||||
TYPE MISMATCH - Color.md:26:7:26:46
|
||||
# PROBLEMS
|
||||
**UNUSED VARIABLE**
|
||||
|
|
|
|||
|
|
@ -234,5 +234,5 @@ CloseCurly(15:1-15:2),EndOfFile(15:2-15:2),
|
|||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(expr @1.1-15.2 (type "{ name: Str, scores: List(Num(_size)), status: [Active({ since: Str })]_others, preferences: { theme: [Dark]_others2, notifications: [Email(Str)]_others3 }, metadata: Result({ tags: List(Str), permissions: List([Read]_others4) }, err), callback: Num(_size2) -> Num(_size3), nested: { items: List([Some(Str)][None]_others5), result: [Success({ data: List(Num(_size4)), timestamp: Str })]_others6 } }"))
|
||||
(expr @1.1-15.2 (type "{ name: Str, scores: List(Num(_size)), status: [Active({ since: Str })]_others, preferences: { theme: [Dark]_others2, notifications: [Email(Str)]_others3 }, metadata: Result({ tags: List(Str), permissions: List([Read]_others4) }, err), callback: _arg -> Num(_size2), nested: { items: List([Some(Str)][None]_others5), result: [Success({ data: List(Num(_size3)), timestamp: Str })]_others6 } }"))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ This expression is used in an unexpected way:
|
|||
^^^^^^^^
|
||||
|
||||
It is of type:
|
||||
_Num(_size), Num(_size2), Num(_size3) -> Num(_size4), Num(_size5) -> Num(_size6) -> _ret_
|
||||
_Num(_size), Num(_size2), _arg -> Num(_size3), _arg2 -> Num(_size4) -> _ret_
|
||||
|
||||
But you are trying to use it as:
|
||||
_Pair(a, b), a -> c, b -> d -> Pair(c, d)_
|
||||
|
|
|
|||
|
|
@ -256,12 +256,12 @@ main! = |_| {
|
|||
(patt @4.1-4.15 (type "_arg -> Num(_size)"))
|
||||
(patt @7.1-7.16 (type "_arg -> _ret"))
|
||||
(patt @10.1-10.18 (type "_arg -> Num(_size)"))
|
||||
(patt @13.1-13.13 (type "Num(_size) -> Num(_size2)"))
|
||||
(patt @13.1-13.13 (type "_arg -> Num(_size)"))
|
||||
(patt @15.1-15.6 (type "_arg -> Num(_size)")))
|
||||
(expressions
|
||||
(expr @4.18-4.24 (type "_arg -> Num(_size)"))
|
||||
(expr @7.19-7.34 (type "_arg -> _ret"))
|
||||
(expr @10.21-10.35 (type "_arg -> Num(_size)"))
|
||||
(expr @13.16-13.35 (type "Num(_size) -> Num(_size2)"))
|
||||
(expr @13.16-13.35 (type "_arg -> Num(_size)"))
|
||||
(expr @15.9-21.2 (type "_arg -> Num(_size)"))))
|
||||
~~~
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue