-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Allow types with the same name at different kinds. #4150
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
Aren't you constantly saying that overloading is a pain to deal with in a compiler? And now you want to add type overloading? Also,
With opaque type aliases, |
And what if they're defined in different scopes and merged by intersection? What should be the resulting variance? Also, FWIW, addressing #2887 doesn't require making overloading usable — just that overloads are kept distinct during typechecking. |
I believe that's pretty much the same thing! Once they are kept distinct, you could argue that it would require additional effort to prevent them from being used. @sjrd Sure, overloading is hard to deal with, but it seems to be the most straightforward way to deal with #2887. I have not seen an alternative solution yet that's more palatable. Btw, this was originally proposed by @DarkDimius and together with @sstucki we agreed that this might be one of the more promising ways to solve this (meaning, we cannot rule it out as impractical immediately). Regarding opaque types, that's easy: Only version of the type may be labelled |
Agreed; I had in mind doing that extra effort to prevent people from (ab)using this feature. If This would allow "kind classes" to include the whole kind. If you only include arities, how sure are we the compiler handles well the intersection of type members with different arities, or that the result is sound? But I admit that right now, playing with intersections a bit, I could only implement trait A {
type T[-X]
}
trait B {
type T[+X]
}
trait C { type M <: A }
trait D { type M >: B }
object Test {
// this should not be callable.
def coerceTArg(x: A with B)(v: x.T[Int]): x.T[String] = { // what's the variance of x.K?
def downcastT[X, Y <: X](a: A)(x: a.T[X]): a.T[Y] = x
def upcastT[X, Y <: X](b: B)(x: b.T[Y]): b.T[X] = x
upcastT[String, Nothing](x)(downcastT[Int, Nothing](x)(v))
}
def test(x: C with D): Unit = {
// XXX try to get funkier behavior following ideas from #2887
}
} |
Famous last words, and I wasn't trying to get a crash, just to exploit a soundness hole (members on lazy vals) where we already have a fix (this was on branch $ dotr -repl
scala> object Test {
trait A {
type T[-X]
}
trait B {
type T[+X]
}
trait C { type M <: A }
trait D { type M >: B }
// this should not be callable.
def coerceTArg(x: A with B)(v: x.T[Int]): x.T[String] = { // what's the variance of x.K?
def downcastT[X, Y <: X](a: A)(x: a.T[X]): a.T[Y] = x
def upcastT[X, Y <: X](b: B)(x: b.T[Y]): b.T[X] = x
upcastT[String, Nothing](x)(downcastT[Int, Nothing](x)(v))
}
def test(x: C with D): Unit = {
// XXX try to get funkier behavior following ideas from #2887
}
}
// defined object Test
scala> import Test._
scala> lazy val v: C with D = v
val v: Test.C & Test.D = <lazy>
scala> Test.coerceTArg(v.M)
Exception in thread "main" java.lang.AssertionError: assertion failed: leak: <error value `M` is not a member of Test.C & Test.D>(x$1) in {
val x$1: <error value `M` is not a member of Test.C & Test.D> = v.M
{
def $anonfun(v: x$1.T[Int]): x$1.T[String] = Test.coerceTArg(x$1)(v)
closure($anonfun): (x$1.T[Int] => x$1.T[String])
}
}
at dotty.DottyPredef$.assertFail(DottyPredef.scala:36)
at dotty.tools.dotc.typer.Typer.ensureNoLocalRefs(Typer.scala:705)
at dotty.tools.dotc.typer.Typer.typedBlock(Typer.scala:667)
at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:1779)
at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1823)
at dotty.tools.dotc.typer.Typer.op1$2(Typer.scala:1854)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1850)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1862)
at dotty.tools.dotc.typer.Typer.adaptNoArgsUnappliedMethod$1(Typer.scala:2301)
at dotty.tools.dotc.typer.Typer.adaptNoArgs$1(Typer.scala:2389)
at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:2522)
at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:2064)
at dotty.tools.dotc.typer.Typer.op1$2(Typer.scala:1854)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1850)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1862)
at dotty.tools.dotc.typer.Namer.typedAheadExpr$$anonfun$1(Namer.scala:1003)
at dotty.tools.dotc.typer.Namer.typedAheadImpl(Namer.scala:993)
at dotty.tools.dotc.typer.Namer.typedAheadExpr(Namer.scala:1003)
at dotty.tools.dotc.typer.Namer.rhsType$2(Namer.scala:1121)
at dotty.tools.dotc.typer.Namer.cookedRhsType$1(Namer.scala:1133)
at dotty.tools.dotc.typer.Namer.lhsType$lzyINIT1$1(Namer.scala:1134)
at dotty.tools.dotc.typer.Namer.lhsType$1(Namer.scala:1134)
at dotty.tools.dotc.typer.Namer.inferredType$1(Namer.scala:1147)
at dotty.tools.dotc.typer.Namer.valOrDefDefSig(Namer.scala:1155)
at dotty.tools.dotc.typer.Namer$Completer.typeSig(Namer.scala:792)
at dotty.tools.dotc.typer.Namer$Completer.completeInCreationContext(Namer.scala:843)
at dotty.tools.dotc.typer.Namer$Completer.complete(Namer.scala:821)
at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeFrom(SymDenotations.scala:247)
at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeInfo$1(SymDenotations.scala:210)
at dotty.tools.dotc.core.SymDenotations$SymDenotation.info(SymDenotations.scala:212)
at dotty.tools.dotc.core.SymDenotations$SymDenotation.ensureCompleted(SymDenotations.scala:353)
at dotty.tools.dotc.typer.Typer.retrieveSym(Typer.scala:1725)
at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:1750)
at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1822)
at dotty.tools.dotc.typer.Typer.op1$2(Typer.scala:1854)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1850)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1862)
at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:1881)
at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:1910)
at dotty.tools.dotc.typer.Typer.typedClassDef(Typer.scala:1451)
at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:1763)
at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1822)
at dotty.tools.dotc.typer.Typer.op1$2(Typer.scala:1854)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1850)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1862)
at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:1881)
at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:1910)
at dotty.tools.dotc.typer.Typer.typedPackageDef(Typer.scala:1623)
at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:1803)
at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1823)
at dotty.tools.dotc.typer.Typer.op1$2(Typer.scala:1854)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1850)
at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1862)
at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:1922)
at dotty.tools.dotc.typer.FrontEnd.typeCheck$$anonfun$1(FrontEnd.scala:66)
at scala.compat.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at dotty.tools.dotc.typer.FrontEnd.monitor(FrontEnd.scala:34)
at dotty.tools.dotc.typer.FrontEnd.typeCheck(FrontEnd.scala:70)
at dotty.tools.repl.REPLFrontEnd.runOn(ReplFrontEnd.scala:25)
at dotty.tools.dotc.Run.runPhases$4$$anonfun$4(Run.scala:170)
at scala.compat.java8.JProcedure1.apply(JProcedure1.java:18)
at scala.compat.java8.JProcedure1.apply(JProcedure1.java:10)
at scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:32)
at scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:29)
at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:191)
at dotty.tools.dotc.Run.runPhases$5(Run.scala:185)
at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:193)
at scala.compat.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:88)
at dotty.tools.dotc.Run.compileUnits(Run.scala:200)
at dotty.tools.dotc.Run.compileUnits(Run.scala:141)
at dotty.tools.repl.ReplCompiler.runCompilationUnit(ReplCompiler.scala:188)
at dotty.tools.repl.ReplCompiler.compile(ReplCompiler.scala:197)
at dotty.tools.repl.ReplDriver.compile(ReplDriver.scala:229)
at dotty.tools.repl.ReplDriver.interpret(ReplDriver.scala:202)
at dotty.tools.repl.ReplDriver.loop$1(ReplDriver.scala:150)
at dotty.tools.repl.ReplDriver.runUntilQuit$$anonfun$1(ReplDriver.scala:154)
at dotty.tools.repl.ReplDriver.withRedirectedOutput$$anonfun$2$$anonfun$1(ReplDriver.scala:163)
at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
at scala.Console$.withErr(Console.scala:192)
at dotty.tools.repl.ReplDriver.withRedirectedOutput$$anonfun$1(ReplDriver.scala:163)
at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
at scala.Console$.withOut(Console.scala:163)
at dotty.tools.repl.ReplDriver.withRedirectedOutput(ReplDriver.scala:163)
at dotty.tools.repl.ReplDriver.runUntilQuit(ReplDriver.scala:154)
at dotty.tools.repl.Main$.main(Main.scala:6)
at dotty.tools.repl.Main.main(Main.scala) |
If two types are in the same "kind class", they are ordered by subtyping (as alluded to by @odersky) and similarly for their variances. Both types and variances form a bounded lattice (at least in theory), so there's always a good candidate for the GLB and LUB of the kinds of types in the same "kind class". In your example, the type members trait A { type T[-X] }
trait B { type T[+X] } are in the same "kind class"
Types with different arities are in different "kind classes", and hence distinguished by overloading resolution. Because they are distinct, there is no intersection — that's precisely the point. The bug you found seems unrelated. The line that causes the error is A note on terminology. I'm really not a fan the term "kind class" because it sounds like it's the analogue of a "type class" on the kind level, which it is definitely not. Personally, I prefer the terms "simple kind" and "simplified kind", i.e. the analogues of "simple types" (as in "the simply typed lambda calculus"), which is precisely what they are. The process of computing the simplified version of a kind is then just "simplification" (though others call it "erasure"). Just my 5cents. |
The concept of simplification does not normally involve a loss of information (the simplified form should be equivalent), but there is a loss of information here. So this seems closer to an "erasure" process indeed. In fact, the analogy is very apt: just like you can't overload two methods with the same type erasure, you cannot overload two types with the same kind erasure! |
Closing as I think this proposal has been abandoned, but feel free to reopen if there's still interest. |
I'd like Scala to allow abstract and alias types with the same name but different kinds. E.g.
Benefits:
The proposal would be to distinguish same-named types by their kind class, where the kind class in this case only talks about arities, but not about bounds and variances. So
are all in different kind classes (and therefore can be defined in the same scope), but
are all in the same kind class, so defining them all in the same scope gives double definition errors. The reason for this distinction is that there is no subtype relation between types in different kind classes, so we can use the kind class as a partition criterion.
One tempting generalization of this scheme is to extend it to traits and classes. E.g. where we write
Function1
, ...,Function22
now, maybe we should allow instead:and so on. But that one is much harder to define and implement. First, what about the companion object? Of which trait is
object Function { ... }
the companion? Second, what about binary names? What are the names of the Java interfaces that theseFunction
traits map to? So for the moment, this generalization is not considered as part of this issue.Besides, one can get a long way already with overloaded aliases. I.e. keep the definitions of the traits
Function1
, ...,Function22
as they are now, but add aliases:and so on.
The text was updated successfully, but these errors were encountered: