-
-
Notifications
You must be signed in to change notification settings - Fork 338
Type Unification #7772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type Unification #7772
Conversation
ccde7bb
to
0cc9a3e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking EXCELLENT!!! 😍
b1ea47e
to
7ddd15f
Compare
7ddd15f
to
1f6b596
Compare
7ac8aac
to
57bdbc0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work on this @jaredramirez!!! 😍 😍 😍
I left some comments, but feel free to merge as-is and address in a separate PR if you prefer! This is awesome!
num_var: TypeIdx, | ||
precision_var: TypeIdx, | ||
num_var: TypeVar, | ||
precision_var: TypeVar, // <- can probably be removed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, why would we remove this? I think we still want it (for int, float, and single quote), for the same reasons we currently do!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rtfeldman Since we aren't representing a number's precision using phantom types anymore, it's unclear to me what this TypeVar
would be pointing to! Given our def of Num
:
pub const Num = union(enum) {
const Self = @This();
flex_var: ?Ident.Idx,
rigid_var: Ident.Idx,
int: Int,
frac: Frac,
/// the Frac data type
pub const Frac = union(enum) {
flex_var: ?Ident.Idx,
rigid_var: Ident.Idx,
exact: Precision,
/// the precision of a frac
pub const Precision = enum { f32, f64, dec };
};
/// the Int data type
pub const Int = union(enum) {
flex_var: ?Ident.Idx,
rigid_var: Ident.Idx,
exact: Precision,
/// the precision of an int
pub const Precision = enum { u8, i8, u16, i16, u32, i32, u64, i64, u128, i128 };
};
};
I don't think we need a precision_var
anymore, since the precision is captured in the num_var
? Very possible I'm missing something though.
pub const Num = union(enum) { | ||
const Self = @This(); | ||
|
||
flex_var, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to flex_var
, I this this will also need a rigid_var: ...
(whatever type we want to use for the interned string name of the rigid var), because you can have Num(a)
where a
appears elsewhere in the type, e.g. in plus : Num(a), Num(a) -> Num(a)
.
Co-authored-by: Richard Feldman <richard@zed.dev> Signed-off-by: Jared Ramirez <jaredpramirez@proton.me>
57bdbc0
to
f32d3da
Compare
fn fresh(self: *Self, vars: *const ResolvedVarDescs, new_content: Content) Var { | ||
const var_ = self.types_store.register(.{ | ||
.content = new_content, | ||
.rank = Rank.min(vars.a.desc.rank, vars.b.desc.rank), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this should be the maximum rank in the entire current pool - but vars
only includes the variables presently unified, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vars.a
and vars.b
are the two types currently being unified, then scratch.fresh_vars
are all intermediate variables created in this pool.
I used min
here because that's what Elm and the rust compiler do. But if it should be max
, lmk and I'm happy to change it!
Type unification
I started this after meeting with Richard It's based on rough plan we discussed there, unification in elm's compiler, and the rust roc compiler.
This implements Hindley-Milner style type unification with extensible records, extensible tag unions, effectful functions, and more!
Overview
It implements type unification for:
Str
,List
,Box
)custom_type
)Differences with Rust Compiler
Also, the zig version does not implement abilities, lambda set variables, or recursion variables since those are either not supported in the new compiler or will work differently and do not need to be handled at the type level.
Overall the rust compiler's unification code is more complex than this Zig version (mainly due to things like recursion vars & lambda sets). As such, this implementation generally errs on the simpler side (basically doing whatever the elm compiler does) when I was in doubt about how something should work.
There are some notable differences compared to the rust implementation:
ext
ensible variable to unwrap tuple types. Current zig implementation does not, and instead simply uses a list of tupleVar
s for the elements.Place in compiler architecture
Additionally, I've hooked up the unify code into the main file tree and setup the types store init with a reference to
ModuleEnv
. Then, all allocations that the types store does uses the allocator fromModuleEnv
. I'm not sure what the broader compiler arch here is – I think Luke mentioned on zulip at one point that the type store may live onModuleEnv
, so happy to change it to that if that is the plan!Review
First off, I'm sorry this PR is so large. It started as a small proof of concept, and just grew and grew. To help orient, here's the rough lay of the land:
src/types/types.zig
: This file contains all of the types that exist in the system, as well as type variables, descriptors and other metadata (such as rank & mark)src/types/store.zig
: This is the types store (sometimes called unification table or union find). It's basically a mapping ofVar -> Descriptor
. There will be 1 types store per module. This store is modified during unification as types are unifiedsrc/check/check_types/unify.zig
: This file the the unification logic it's self, which is the lion-share of the work here. About the first 1500 lines are the implementation, then the other 2000 are tests 😵💫src/check/canonicalize/IR.zig
: This file is updated to reference the new number types intypes/types.zig
instead of the oldtypes/num.zig
typesAny and all feedback is welcome! This is far and a way the most zig I've ever written, so please let me know where things can be done better.
Open Questions
precision_var
that I've left, but I think they can be removed since numbers with variable precision do not use a full-fledgedVar
to represent the precision.Var
that's unified separately because this is how the rust compiler work, but I think it could be managed via an enum similar to numbers. I would like to make this change, but I'm unsure if there are down-stream type implications.