Support czer + type checking out of order defs

This commit is contained in:
Jared Ramirez 2025-11-05 17:14:04 -05:00
parent fe67563329
commit 5570184040
No known key found for this signature in database
GPG key ID: 41158983F521D68C
3 changed files with 94 additions and 180 deletions

View file

@ -102,11 +102,14 @@ static_dispatch_method_name_buf: std.ArrayList(u8),
/// Map representation of Ident -> Var, used in checking static dispatch constraints
ident_to_var_map: std.AutoHashMap(Ident.Idx, Var),
/// Map representation all top level patterns, and if we've processed them yet
top_level_ptrns: std.AutoHashMap(CIR.Pattern.Idx, HasProcessed),
top_level_ptrns: std.AutoHashMap(CIR.Pattern.Idx, DefProcessed),
/// A map of rigid variables that we build up during a branch of type checking
const FreeVar = struct { ident: base.Ident.Idx, var_: Var };
/// A def + processing data
const DefProcessed = struct { def_idx: CIR.Def.Idx, status: HasProcessed };
/// Indicates if something has been processed or not
const HasProcessed = enum { processed, processing, not_processed };
@ -173,7 +176,7 @@ pub fn init(
.bool_var = undefined, // Will be initialized in copyBuiltinTypes()
.static_dispatch_method_name_buf = try std.ArrayList(u8).initCapacity(gpa, 32),
.ident_to_var_map = std.AutoHashMap(Ident.Idx, Var).init(gpa),
.top_level_ptrns = std.AutoHashMap(CIR.Pattern.Idx, HasProcessed).init(gpa),
.top_level_ptrns = std.AutoHashMap(CIR.Pattern.Idx, DefProcessed).init(gpa),
};
}
@ -609,8 +612,10 @@ fn setVarRank(self: *Self, target_var: Var, env: *Env) std.mem.Allocator.Error!v
self.types.setDescRank(resolved.desc_idx, env.rank());
try env.var_pool.addVarToRank(target_var, env.rank());
} else {
// TODO: Is this a bug?
// std.debug.panic("INVALID SET VAR RANK\n", .{});
if (builtin.mode == .Debug) {
std.debug.panic("trying to set rank of var {}, but var is a redirect", .{@intFromEnum(target_var)});
}
try self.unifyWith(target_var, .err, env);
}
}
@ -697,7 +702,7 @@ pub fn checkFile(self: *Self) std.mem.Allocator.Error!void {
const defs_slice = self.cir.store.sliceDefs(self.cir.all_defs);
for (defs_slice) |def_idx| {
const def = self.cir.store.getDef(def_idx);
try self.top_level_ptrns.put(def.pattern, .not_processed);
try self.top_level_ptrns.put(def.pattern, .{ .def_idx = def_idx, .status = .not_processed });
}
// Then, iterate over defs again, inferring types
@ -762,12 +767,15 @@ fn checkDef(self: *Self, def_idx: CIR.Def.Idx, env: *Env) std.mem.Allocator.Erro
const ptrn_var = ModuleEnv.varFrom(def.pattern);
const expr_var = ModuleEnv.varFrom(def.expr);
if (self.top_level_ptrns.get(def.pattern) == .processed) {
// If we've already processed this def, return immediately
return;
if (self.top_level_ptrns.get(def.pattern)) |processing_def| {
if (processing_def.status == .processed) {
// If we've already processed this def, return immediately
return;
}
}
try self.top_level_ptrns.put(def.pattern, .processing);
// Make as processing
try self.top_level_ptrns.put(def.pattern, .{ .def_idx = def_idx, .status = .processing });
{
try env.var_pool.pushRank();
@ -820,7 +828,7 @@ fn checkDef(self: *Self, def_idx: CIR.Def.Idx, env: *Env) std.mem.Allocator.Erro
}
// Mark as processed
try self.top_level_ptrns.put(def.pattern, .processed);
try self.top_level_ptrns.put(def.pattern, .{ .def_idx = def_idx, .status = .processed });
}
// create types for type decls //
@ -2519,6 +2527,22 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, expected: Expected)
const pat_var = ModuleEnv.varFrom(lookup.pattern_idx);
const resolved_pat = self.types.resolveVar(pat_var).desc;
const mb_processing_def = self.top_level_ptrns.get(lookup.pattern_idx);
if (mb_processing_def) |processing_def| {
switch (processing_def.status) {
.not_processed => {
var sub_env = try self.env_pool.acquire(.generalized);
defer self.env_pool.release(sub_env);
try self.checkDef(processing_def.def_idx, &sub_env);
},
.processing => {
// TODO: Handle recursive defs
},
.processed => {},
}
}
if (resolved_pat.rank == Rank.generalized) {
const instantiated = try self.instantiateVar(pat_var, env, .use_last_var);
_ = try self.unify(expr_var, instantiated, env);
@ -3161,8 +3185,11 @@ fn checkBlockStatements(self: *Self, statements: []const CIR.Statement.Idx, env:
_ = try self.unify(stmt_var, var_expr, env);
},
.s_reassign => |reassign| {
// Check the pattern
try self.checkPattern(reassign.pattern_idx, env, .no_expectation);
// We don't need to check the pattern here since it was already
// checked when this var was created.
//
// try self.checkPattern(reassign.pattern_idx, env, .no_expectation);
const reassign_pattern_var: Var = ModuleEnv.varFrom(reassign.pattern_idx);
does_fx = try self.checkExpr(reassign.expr, env, .no_expectation) or does_fx;
@ -3976,14 +4003,21 @@ fn checkDeferredStaticDispatchConstraints(self: *Self, env: *Env) std.mem.Alloca
if (is_this_module) {
// Check if we've processed this def already.
const def = original_env.store.getDef(def_idx);
const mb_processing_status = self.top_level_ptrns.get(def.pattern);
if (mb_processing_status == .processing) {
// TODO: Handle recursive defs
} else if (mb_processing_status == .not_processed) {
var sub_env = try self.env_pool.acquire(.generalized);
defer self.env_pool.release(sub_env);
const mb_processing_def = self.top_level_ptrns.get(def.pattern);
if (mb_processing_def) |processing_def| {
std.debug.assert(processing_def.def_idx == def_idx);
switch (processing_def.status) {
.not_processed => {
var sub_env = try self.env_pool.acquire(.generalized);
defer self.env_pool.release(sub_env);
try self.checkDef(def_idx, &sub_env);
try self.checkDef(def_idx, &sub_env);
},
.processing => {
// TODO: Handle recursive defs
},
.processed => {},
}
}
}

View file

@ -22,12 +22,13 @@ int_container = make_container(num)
str_container = make_container(str)
list_container = make_container(my_empty_list)
# TODO
# Polymorphic record update
update_data = |container, new_value| { container & data: new_value }
# update_data = |container, new_value| { container & data: new_value }
# Used with different record types
updated_int = update_data(int_container, 100)
updated_str = update_data(str_container, "world")
# updated_int = update_data(int_container, 100)
# updated_str = update_data(str_container, "world")
# Function returning polymorphic record
identity_record = |x| { value: x }
@ -43,84 +44,9 @@ main = |_| {
}
~~~
# EXPECTED
UNEXPECTED TOKEN IN EXPRESSION - let_polymorphism_records.md:19:50:19:51
UNRECOGNIZED SYNTAX - let_polymorphism_records.md:19:50:19:51
UNUSED VARIABLE - let_polymorphism_records.md:19:52:19:67
UNUSED VARIABLE - let_polymorphism_records.md:19:27:19:36
UNUSED VALUE - let_polymorphism_records.md:19:40:19:49
TYPE MISMATCH - let_polymorphism_records.md:19:58:19:67
NIL
# PROBLEMS
**UNEXPECTED TOKEN IN EXPRESSION**
The token **&** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**let_polymorphism_records.md:19:50:19:51:**
```roc
update_data = |container, new_value| { container & data: new_value }
```
^
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
**let_polymorphism_records.md:19:50:19:51:**
```roc
update_data = |container, new_value| { container & data: new_value }
```
^
This might be a syntax error, an unsupported language feature, or a typo.
**UNUSED VARIABLE**
Variable `data` is not used anywhere in your code.
If you don't need this variable, prefix it with an underscore like `_data` to suppress this warning.
The unused variable is declared here:
**let_polymorphism_records.md:19:52:19:67:**
```roc
update_data = |container, new_value| { container & data: new_value }
```
^^^^^^^^^^^^^^^
**UNUSED VARIABLE**
Variable `new_value` is not used anywhere in your code.
If you don't need this variable, prefix it with an underscore like `_new_value` to suppress this warning.
The unused variable is declared here:
**let_polymorphism_records.md:19:27:19:36:**
```roc
update_data = |container, new_value| { container & data: new_value }
```
^^^^^^^^^
**UNUSED VALUE**
This expression produces a value, but it's not being used:
**let_polymorphism_records.md:19:40:19:49:**
```roc
update_data = |container, new_value| { container & data: new_value }
```
^^^^^^^^^
It has the type:
__a_
**TYPE MISMATCH**
This expression is used in an unexpected way:
**let_polymorphism_records.md:19:58:19:67:**
```roc
update_data = |container, new_value| { container & data: new_value }
```
^^^^^^^^^
It has the type:
_new_value_
But I expected it to be:
_new_value_
NIL
# TOKENS
~~~zig
KwApp,OpenSquare,LowerIdent,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,
@ -133,9 +59,6 @@ LowerIdent,OpAssign,OpBar,LowerIdent,OpBar,OpenCurly,LowerIdent,OpColon,LowerIde
LowerIdent,OpAssign,LowerIdent,NoSpaceOpenRound,LowerIdent,CloseRound,
LowerIdent,OpAssign,LowerIdent,NoSpaceOpenRound,LowerIdent,CloseRound,
LowerIdent,OpAssign,LowerIdent,NoSpaceOpenRound,LowerIdent,CloseRound,
LowerIdent,OpAssign,OpBar,LowerIdent,Comma,LowerIdent,OpBar,OpenCurly,LowerIdent,OpAmpersand,LowerIdent,OpColon,LowerIdent,CloseCurly,
LowerIdent,OpAssign,LowerIdent,NoSpaceOpenRound,LowerIdent,Comma,Int,CloseRound,
LowerIdent,OpAssign,LowerIdent,NoSpaceOpenRound,LowerIdent,Comma,StringStart,StringPart,StringEnd,CloseRound,
LowerIdent,OpAssign,OpBar,LowerIdent,OpBar,OpenCurly,LowerIdent,OpColon,LowerIdent,CloseCurly,
LowerIdent,OpAssign,LowerIdent,NoSpaceOpenRound,Int,CloseRound,
LowerIdent,OpAssign,LowerIdent,NoSpaceOpenRound,StringStart,StringPart,StringEnd,CloseRound,
@ -203,31 +126,6 @@ EndOfFile,
(e-apply
(e-ident (raw "make_container"))
(e-ident (raw "my_empty_list"))))
(s-decl
(p-ident (raw "update_data"))
(e-lambda
(args
(p-ident (raw "container"))
(p-ident (raw "new_value")))
(e-block
(statements
(e-ident (raw "container"))
(e-malformed (reason "expr_unexpected_token"))
(s-type-anno (name "data")
(ty-var (raw "new_value")))))))
(s-decl
(p-ident (raw "updated_int"))
(e-apply
(e-ident (raw "update_data"))
(e-ident (raw "int_container"))
(e-int (raw "100"))))
(s-decl
(p-ident (raw "updated_str"))
(e-apply
(e-ident (raw "update_data"))
(e-ident (raw "str_container"))
(e-string
(e-string-part (raw "world")))))
(s-decl
(p-ident (raw "identity_record"))
(e-lambda
@ -289,15 +187,13 @@ int_container = make_container(num)
str_container = make_container(str)
list_container = make_container(my_empty_list)
# TODO
# Polymorphic record update
update_data = |container, new_value| {
container
data : new_value
}
# update_data = |container, new_value| { container & data: new_value }
# Used with different record types
updated_int = update_data(int_container, 100)
updated_str = update_data(str_container, "world")
# updated_int = update_data(int_container, 100)
# updated_str = update_data(str_container, "world")
# Function returning polymorphic record
identity_record = |x| { value: x }
@ -369,44 +265,6 @@ main = |_| {
(p-assign (ident "make_container")))
(e-lookup-local
(p-assign (ident "my_empty_list")))))
(d-let
(p-assign (ident "data"))
(e-anno-only)
(annotation
(ty-rigid-var (name "new_value"))))
(d-let
(p-assign (ident "update_data"))
(e-lambda
(args
(p-assign (ident "container"))
(p-assign (ident "new_value")))
(e-block
(s-expr
(e-lookup-local
(p-assign (ident "container"))))
(s-expr
(e-runtime-error (tag "expr_not_canonicalized")))
(s-let
(p-assign (ident "data"))
(e-anno-only))
(e-empty_record))))
(d-let
(p-assign (ident "updated_int"))
(e-call
(e-lookup-local
(p-assign (ident "update_data")))
(e-lookup-local
(p-assign (ident "int_container")))
(e-num (value "100"))))
(d-let
(p-assign (ident "updated_str"))
(e-call
(e-lookup-local
(p-assign (ident "update_data")))
(e-lookup-local
(p-assign (ident "str_container")))
(e-string
(e-literal (string "world")))))
(d-let
(p-assign (ident "identity_record"))
(e-lambda
@ -473,10 +331,6 @@ main = |_| {
(patt (type "{ count: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])], data: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])] }"))
(patt (type "{ count: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])], data: Str }"))
(patt (type "{ count: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])], data: List(_elem) }"))
(patt (type "Error"))
(patt (type "_arg, _arg2 -> {}"))
(patt (type "{}"))
(patt (type "{}"))
(patt (type "a -> { value: a }"))
(patt (type "{ value: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])] }"))
(patt (type "{ value: Str }"))
@ -492,10 +346,6 @@ main = |_| {
(expr (type "{ count: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])], data: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])] }"))
(expr (type "{ count: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])], data: Str }"))
(expr (type "{ count: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])], data: List(_elem) }"))
(expr (type "Error"))
(expr (type "_arg, _arg2 -> {}"))
(expr (type "{}"))
(expr (type "{}"))
(expr (type "a -> { value: a }"))
(expr (type "{ value: num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])] }"))
(expr (type "{ value: Str }"))

View file

@ -14,13 +14,26 @@ Foo := [Whatever].{
transform = |x| x
}
result2 : Foo.Bar
result2 = result
result : Foo.Bar
result = Foo.transform(Foo.defaultBar)
~~~
# EXPECTED
NIL
# PROBLEMS
NIL
**UNDEFINED VARIABLE**
Nothing is named `result` in this scope.
Is there an `import` or `exposing` missing up-top?
**nominal_associated_lookup_mixed.md:11:11:11:17:**
```roc
result2 = result
```
^^^^^^
# TOKENS
~~~zig
UpperIdent,OpColonEqual,OpenSquare,UpperIdent,CloseSquare,Dot,OpenCurly,
@ -30,6 +43,8 @@ LowerIdent,OpColon,UpperIdent,NoSpaceDotUpperIdent,OpArrow,UpperIdent,NoSpaceDot
LowerIdent,OpAssign,OpBar,LowerIdent,OpBar,LowerIdent,
CloseCurly,
LowerIdent,OpColon,UpperIdent,NoSpaceDotUpperIdent,
LowerIdent,OpAssign,LowerIdent,
LowerIdent,OpColon,UpperIdent,NoSpaceDotUpperIdent,
LowerIdent,OpAssign,UpperIdent,NoSpaceDotLowerIdent,NoSpaceOpenRound,UpperIdent,NoSpaceDotLowerIdent,CloseRound,
EndOfFile,
~~~
@ -66,6 +81,11 @@ EndOfFile,
(args
(p-ident (raw "x")))
(e-ident (raw "x"))))))
(s-type-anno (name "result2")
(ty (name "Foo.Bar")))
(s-decl
(p-ident (raw "result2"))
(e-ident (raw "result")))
(s-type-anno (name "result")
(ty (name "Foo.Bar")))
(s-decl
@ -83,6 +103,9 @@ Foo := [Whatever].{
transform = |x| x
}
result2 : Foo.Bar
result2 = result
result : Foo.Bar
result = Foo.transform(Foo.defaultBar)
~~~
@ -104,6 +127,11 @@ result = Foo.transform(Foo.defaultBar)
(ty-fn (effectful false)
(ty-lookup (name "Foo.Bar") (local))
(ty-lookup (name "Foo.Bar") (local)))))
(d-let
(p-assign (ident "result2"))
(e-runtime-error (tag "ident_not_in_scope"))
(annotation
(ty-lookup (name "Foo.Bar") (local))))
(d-let
(p-assign (ident "result"))
(e-call
@ -130,6 +158,7 @@ result = Foo.transform(Foo.defaultBar)
(defs
(patt (type "Foo.Bar"))
(patt (type "Foo.Bar -> Foo.Bar"))
(patt (type "Error"))
(patt (type "Foo.Bar")))
(type_decls
(nominal (type "Foo")
@ -139,5 +168,6 @@ result = Foo.transform(Foo.defaultBar)
(expressions
(expr (type "Foo.Bar"))
(expr (type "Foo.Bar -> Foo.Bar"))
(expr (type "Error"))
(expr (type "Foo.Bar"))))
~~~