-
Notifications
You must be signed in to change notification settings - Fork 191
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
Fe type/trait system #931
Fe type/trait system #931
Conversation
f96e27c
to
69bcd95
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.
looks great
I concur, this is great. I've been writing some fe code and checking out the error messages, and they're pretty nice.
|
I think this syntax would be fine. What do you think about a "type hole" syntax, for example:
|
┌─ hkt_bound.fe:1:17 | ||
│ | ||
1 │ pub trait Trait<T: HktTrait> {} | ||
│ ^ expected `(* -> *)` kind, but `T` has `*` kind |
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 error is confusing to me. Is this because any generic type T
has kind *
by default, unless explicitly specified? Changing this to T: HktTrait + * -> *
eliminates the error. However, T: HktTrait
should imply that T: * -> *
. Is this too complicated to deduce? (Not a major issue of course, I'm mostly curious)
expression: diags | ||
input_file: crates/uitest/fixtures/ty/alias_non_mono.fe | ||
--- | ||
|
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.
Should there be an error here?
Reports an error for unused type paramsThis is not necessary; need to summarize the reasoning instead.NOTE: This PR doesn't contain dependent types and function types implementation, please refer to #940
Fe type/trait system
This PR contains Fe v2 type/trait system implementations, which includes
Overview from high-level perspective
Recursive type
We don't allow recursive types if the recursion forms "direct" dependency since it leads to infinite-sized types in our type system.
e.g.,
But this restriction is mitigated by using "indirect" wrapper type, e.g., pointer or reference(NOTE: reference is not implemented in this PR).
e.g.,
Generics
struct
,enum
,function
, andtrait
can be generic, meaning they can have type parameters; also, type parameters can have trait bounds in both a type parameter list and where clause. e.g.,Higher Kinded Types
In fe v2, we support HKT, which means we can abstract over a type that also abstracts over some type.
To specify a HKT, users need to annotate the type parameter with an explicit kind. The type parameter is considered a
*
type if there is no explicit kind bound.e.g.,
NOTE: all terms need to be typed to
*
kind type, e.g.,Trait
The trait system in fe is similar to rust, but we don't support associated types yet.
On the contrary, Fe allows for a trait to specify the kind of its type parameters and self type(the type that implements the trait) in addition to a normal trait bound.
NOTE:
Self
appearing in a trait is treated just as a type parameter.e.g.,
Also, it's possible to specify a super trait in the trait definition(NOTE: This is just a syntax sugar for
Self: SuperTrait
.but cyclic trait relationship is not allowed. e.g.,
Trait impl
It's the same with the Rust.
NOTE: You can implement a trait on a generic type. e.g.,
Trait impl conflict
Conflict happens iff
e.g.,
We don't consider trait bounds in the impl conflict check, so the example below is considered an error.
Trait Bound Satisfiability
Trait-bound satisfiability check is performed for both type and trait instances.
NOTE: Trait sat solver can use a super trait relationship, so the below example works fine.
Type Alias
Type aliases are NOT in the Fe type system, i.e., They are a kind of macro and expanded at the call site.
As a result, All generic parameters of a type aliases must be given at the use site.
e.g.,
Also, specifying kind bounds is allowed at the type alias definition, but trait bounds are not allowed.
Alias to a type that has a higher kind is allowed.
Implementation Note
The design of this PR is greatly inspired by Typing Haskell in Haskell. The main difference is that the PR extended this paper to allow multiple trait parameters.
Type Definitions
All type-related definitions are implemented in
hir_analysis::ty::ty_def
.TyVar
represents a free type variable primarily used for type inference and type generalization. For example, when we have aResult<T, E>
, generalization lifts it to Result<TyVar<0>, TyVar<1>>. It's important to note thatTyParam
is not a free type variable. That is, there is no essential difference betweenTyParam
andi32
, both are strictly different types, and unification will always fail.TyCon
represents concrete type likei32
orResult
.TyApp
represents type application. if you seeResult<i32, Option<i32>
, then the internal representation for the type isTyApp(TyApp(Result, i32), TyApp(Option, i32))
.Unification
Unification-related types are implemented in
hir_analysis::ty::unify
.Even though we haven't implemented a type inference, unification plays an important role both for the trait bound satisfiability solver and a trait impl conflict check.
(TBW).
Trait Impl conflict check
Two trait implementation(
impl Trait1 for Ty1
andimpl Trait1 for Ty2
) is considered conflict iffTrait1
andTrait2
can be unified.Ty1
andTy2
can be unified.NOTE: Before the unifiability check, we need to generalize two impls.
When we check conflicts for
1.
impl Trait<i32> for u32
2.
impl Trait<T> for T
then generalized implementation would be
1'.
impl Trait<i32> for u32
2'.
impl Trait<TyVar(0)> for TyVar(0)
In the trait unification phase,
TyVar(0)
is unified toi32
. Then, this will lead to a unification error in the implementor type unification phase. As a result, the two implementations do NOT conflict.(TBW).
Known Issues
HKT-specific SAT problem
It's known that HKT makes it difficult to check trait-bound satisfiability.
e.g.,
In this example, the compiler doesn't emit any error. To address this, we need to perform a satisfiability check after monomorphization. But it's impractical. After all, we have two options to address this,
Even if option
2.
is selected, safety will not be jeopardized. This is because, either way, it is impossible to create a value with this unsatisfiable (unsat) type. Practically speaking, to create an instance ofS2
, some constraints onT
should be required. For example,T: Applicative
like the below one:Since
S1
cannot implementApplicative
due to trait bound limitations onS1
, a value ofS2<S1>
can't exist.But this could become a problem if unsafe cast is allowed in Fe. This is something we need to discuss.
A syntax for type arguments
Since we decided to support HKT, the
<>
argument list is not perfectly suitable, especially with type aliases.e.g.,
The problem is
s: Foo<i32, i256>
seems somewhat unnatural.This could be mitigated to a certain extent by allowing to write
Foo<i32><i256>
.MISC
In the current implementation, unused generic parameters are allowed. The context about why rust disallows unused type parameters is described here
I think this might be problematic when we start implementing the borrow checker; either way, we need to decide how to handle this.