-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Reducing open types is potentially unsafe #2771
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
Comments
Here it seems that I see how you can try to cheat around this. But it seems that, at compile time, whenever narrowing produces a statically undefined kind That is, even if you cheat like this, the compiler never sees ill-kinded types, and at runtime def test(x: C): Unit = {
//here x.M <: A. If y: x.M, then y.L has a *defined* kind * -> *:
def f(y: x.M)(z: y.L[Any]): y.L[Any] = z
}
def call(w: C with D) = test(w)
} To show the next problem, take the next example and try inferring the return type for def test(x: C)(y: x.M)(z: y.L[Any]): y.L[Any] = z
def call1(w: C with D) = test(w)
def call2(w: C with D)(y: w.M)(z: y.L[Any]) = test(w)(y)(z) What do we know about typechecking non-higher-kinded DOT? Kinds don't show up, but inconsistent bounds and open terms do. Yet, if we declare "inconsistent kinds" a static error, substitution in types can produce invalid types and we must detect them. But DOT seems to make lots of effort to make types valid. Rompf and Amin (OOPSLA 2016) give a narrowing lemma without restrictions (Lemma 3), which would not hold for the example with |
Yes, indeed. Though it's questionable whether the compiler could (and should) determine this statically.
No, I don't think so. The kind of I think what makes this example confusing is that |
As you say, reduction of open types is not (kind-)safe and can get stuck/fail. Yet, compiling some examples needs reduction of open types (see below). So it seems some of the failures arising from bad types must be detected before leading to such bad behavior, doesn't it? Without reduction of open types, it's not clear one can deal with such an example (in particular, kind-checking trait A { type L[X] }
trait B { type L }
trait C { type M <: A }
def test(x: C): Unit = {
def f(y: x.M)(z: y.L[Any]): y.L[Any] = z
} Regarding "undefined kinds" — I expect some code needs to know the (most specific) kind of
Without kinds, up to now, we haven't seen bad bounds trigger errors during compile-time type reduction, for one reason or the other. Even if you prove At least as of 6f8bc43, class TypeError {
type Absurd = Any { type X >: String <: Int }
def typesafeCast(x: Absurd, y: String): Int = y: x.X // this cast is needed to exploit the absurdity.
def foo(x: Absurd) = typesafeCast(x, "") + 1
} |
Good points @Blaisorblade.
Yes, this seems like a problem and I don't know how best to address it.
OK, I'll use that slack to interpret your "most specific kind" as "synthesized kind" and then it seems the only viable option here is
That is a very good point. Evaluation of open terms is of course not safe either, which should be a problem for optimizations such as constant folding, as you suggest. I haven't tried this but it could come down to a |
I know even less on Dotty, but now I wonder if widening BTW, I suspect research on validated metaprograms on DOT (or subsets) might be worthwhile to investigate such issues (though I'm not sure how popular it would be)—there's more to a compiler than a sound type system, though that helps. I've been thinking about this for a while, though of course it's hard. I use "validated" to allow for a range of options (including forms of systematic testing), but a verified constant folder would not sound stupid—bonus points with some normalization algorithms. |
This is likely related, though the error is different: #2887. |
The following currently causes a stack overflow in dotty (it's rejected by scalac):
The overflow is caused by the compiler attempting to evaluate the non-terminating type expression
y.L[y.L]
which is ill-kinded.The type definition
type L[F[_]] = F[F] }
is clearly ill-kinded already and scalac actually reports this.But the deeper issue is in the line above that definition: the type
y.L
has two conflicting kinds, it is both a type operator and a proper type. This is becauseL
is declared as a type operator in traitA
and as a proper type in traitB
, which are the upper and lower bounds ofx.M
, respectively. So we can construct an instancey
ofB
wherey.L
is a proper type and then apply it to some other type by up-castingy
tox.M
. Here's a variant of the example which illustrates that point more clearly:It is rejected with the rather absurd error message
Changing the type of
z
toy.L
results instead inThis is another instance of a know problem about type members with absurd bounds (see also issue #50): evaluation is unsafe in a context containing variables/values (here
x
) of a type (hereC & D
) containing type members with absurd bounds (herex.M
). In this particular case, the unsafe evaluation happens in an open type (namelyy.L[Any]
) rather than an open term, and at compile time (rather than at runtime).Clarification: by "open type" I mean a type that contains free type or term variables (as opposed to a closed type which contains no free variables).
The text was updated successfully, but these errors were encountered: