Super trait bounds are not checked
Opened this issue · 3 comments
prim type U32
prim type Str
prim fn printStr(s: Str)
trait A[T1]:
fn a(self)
trait B[T2]:
fn b(self)
type Box[T3]:
thing: T3
impl[T4: A] B for Box[T4]:
fn b(self) = printStr("In b")
fn main() =
Box(thing = 123u32).b()
# args: --typecheck --no-prelude --no-backtrace
In this test, the Box[T].b
call should check T: A
, but current it doesn't.
The problem is when calling Scheme::subst
in select_method
:
Lines 306 to 326 in aba5e91
to substitute quantified types of the method's type with instantiation types we ignore the bounds on the substituted quantified vars.
Fixing this is a bit involved because this is the first time we need to reduce a predicate of syntax <Type> : <Bound>
to a set of normal predicates of syntax <Variable> : <Bound>
.
A common case where this issue will cause problems is in for
loops, which require the iterated expression to implement Iterator
:
type A:
x: U32
fn main() =
for x in range(A(x = 1), A(x = 2)): # should be type error
printStr("")
where range
is defined as:
fn range[T](start: T, end: T): RangeIterator[T] =
RangeIterator(current = start, end = end)
type RangeIterator[T]:
...
impl[T: Step + Ord] Iterator for RangeIterator[T]:
...
Currently we don't check the T: Step + Ord
bound and this type checks, and causes a crash in monomorphiser.
Currently if I have a quantified varible like
fn id[T](a: T): T = a
When checking the function body, I represent T
as a type constructor with no constructors.
(The bounds are collected in a "context" that we don't use when type checking the body. The context is only used when resolving the predicates collected while checking the body.)
I think this is mostly fine, but I'm wondering if it's better in any way to represent these quantified variables as their own variant.
More concretely, in #26 we will have to "reduce" predicates of form <Type> : <Bound>
to a set of predicates of form <Var> : <Bound>
.
In this reduction, if we see a bound like T : MyTrait
, we may want to check whether T
is a type constructor or a type parameter.
-
If type parameter: then the bound needs to be in the context.
(We currently don't pass context around when checking bodies, we probably should.)
-
If type constructor: the type should be implementing the trait, which we can check by looking at the implementing types of the trait in
PgmTypes
.(Note: current we don't allow a polymorphic type to implement a trait multiple times with different type parameters, so this is easy.)
If we treat both the same, and starting passing context around, then for a type like T
we have to check both the context and global type state to see if satisfy the bounds.
It's fine as long as we check the context first and then the global type state, but if we had two different variants we would look one or the other depending on the variant.
If we treat both the same, and starting passing context around, then for a type like T we have to check both the context and global type state to see if satisfy the bounds.
It's fine as long as we check the context first and then the global type state, but if we had two different variants we would look one or the other depending on the variant.
To clarify, the reason why we need to pass conetxt around is that the predicate set type (PredSet
) only allows adding predicates to unification variables.
So I can't add predicates to T
in fn a[T](t: T) = <HERE>
and then check that the predicates hold in check_fun
or similar.