Use bidirectional annotation checking

This commit is contained in:
Richard Feldman 2025-08-15 14:51:52 -04:00
parent 4242bb621c
commit dbc44e0236
No known key found for this signature in database
13 changed files with 383 additions and 74 deletions

View file

@ -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 {

View file

@ -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)"))))
~~~

View file

@ -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)"))
~~~

View file

@ -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)"))
~~~

View file

@ -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) }"))
~~~

View file

@ -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"))))
~~~

View file

@ -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)"))
~~~

View 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"))))
~~~

View file

@ -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)"))))

View file

@ -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**

View file

@ -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 } }"))
~~~

View file

@ -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)_

View file

@ -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)"))))
~~~