From ed445d8fd29220cf6b12be9c5fdcb28d9e50ad9f Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 15:58:52 -0400 Subject: [PATCH 01/17] Create tests to cover various feature interactions with precise --- sbt-test/precise/moduletest/build.sbt | 1 + .../src/main/scala/precise-main.scala | 9 + .../src/test/scala/precise-test.scala | 7 + sbt-test/precise/moduletest/test | 2 + tests/neg/precise-alias-extends.check | 68 +++ tests/neg/precise-alias-extends.scala | 93 ++++ tests/neg/precise-override.check | 24 + tests/neg/precise-override.scala | 40 ++ tests/neg/precise-typecheck.check | 18 + tests/neg/precise-typecheck.scala | 489 ++++++++++++++++++ tests/pos/precise-tc/precise-lib_1.scala | 28 + tests/pos/precise-tc/precise-test_2.scala | 4 + 12 files changed, 783 insertions(+) create mode 100644 sbt-test/precise/moduletest/build.sbt create mode 100644 sbt-test/precise/moduletest/src/main/scala/precise-main.scala create mode 100644 sbt-test/precise/moduletest/src/test/scala/precise-test.scala create mode 100644 sbt-test/precise/moduletest/test create mode 100644 tests/neg/precise-alias-extends.check create mode 100644 tests/neg/precise-alias-extends.scala create mode 100644 tests/neg/precise-override.check create mode 100644 tests/neg/precise-override.scala create mode 100644 tests/neg/precise-typecheck.check create mode 100644 tests/neg/precise-typecheck.scala create mode 100644 tests/pos/precise-tc/precise-lib_1.scala create mode 100644 tests/pos/precise-tc/precise-test_2.scala diff --git a/sbt-test/precise/moduletest/build.sbt b/sbt-test/precise/moduletest/build.sbt new file mode 100644 index 000000000000..63e314982c41 --- /dev/null +++ b/sbt-test/precise/moduletest/build.sbt @@ -0,0 +1 @@ +scalaVersion := sys.props("plugin.scalaVersion") diff --git a/sbt-test/precise/moduletest/src/main/scala/precise-main.scala b/sbt-test/precise/moduletest/src/main/scala/precise-main.scala new file mode 100644 index 000000000000..e76c337b7c1a --- /dev/null +++ b/sbt-test/precise/moduletest/src/main/scala/precise-main.scala @@ -0,0 +1,9 @@ +object preciseLib: + import scala.annotation.precise + + object internals: + trait Foo[V] + def tester[@precise V](v: V): Foo[V] = ??? + extension [T](t: T) def testerExt[@precise V](v: V): Foo[V] = ??? + + export internals.* \ No newline at end of file diff --git a/sbt-test/precise/moduletest/src/test/scala/precise-test.scala b/sbt-test/precise/moduletest/src/test/scala/precise-test.scala new file mode 100644 index 000000000000..41e4534b7259 --- /dev/null +++ b/sbt-test/precise/moduletest/src/test/scala/precise-test.scala @@ -0,0 +1,7 @@ +import preciseLib.* + +val v1 = tester(1) +val v1Test: Foo[1] = v1 + +val v2 = "hi".testerExt(2) +val v2Test: Foo[2] = v2 \ No newline at end of file diff --git a/sbt-test/precise/moduletest/test b/sbt-test/precise/moduletest/test new file mode 100644 index 000000000000..4795cbd8fe55 --- /dev/null +++ b/sbt-test/precise/moduletest/test @@ -0,0 +1,2 @@ +> Test/compile + diff --git a/tests/neg/precise-alias-extends.check b/tests/neg/precise-alias-extends.check new file mode 100644 index 000000000000..b46c4346a35b --- /dev/null +++ b/tests/neg/precise-alias-extends.check @@ -0,0 +1,68 @@ +-- Error: tests/neg/precise-alias-extends.scala:26:28 ------------------------------------------------------------------ +26 | type FooInvAlias[@precise A] = FooInv[A] // error + | ^^^^^^^^^^ + | precise type parameter A occurs in imprecise position in preciseTypeAliasExtendMorePrecise.FooInv[A] +-- Error: tests/neg/precise-alias-extends.scala:27:36 ------------------------------------------------------------------ +27 | opaque type FooInvOpaque[@precise A] = FooInv[A] // error + | ^^^^^^^^^^ + | precise type parameter A occurs in imprecise position in preciseTypeAliasExtendMorePrecise.FooInv[A] +-- Error: tests/neg/precise-alias-extends.scala:32:29 ------------------------------------------------------------------ +32 | type FooCovAlias[@precise +A] = FooCov[A] // error + | ^^^^^^^^^^^ + | precise type parameter A occurs in imprecise position in preciseTypeAliasExtendMorePrecise.FooCov[A] +-- Error: tests/neg/precise-alias-extends.scala:33:37 ------------------------------------------------------------------ +33 | opaque type FooCovOpaque[@precise +A] = FooCov[A] // error + | ^^^^^^^^^^^ + | precise type parameter A occurs in imprecise position in preciseTypeAliasExtendMorePrecise.FooCov[A] +-- Error: tests/neg/precise-alias-extends.scala:37:29 ------------------------------------------------------------------ +37 | type FooConAlias[@precise -A] = FooCon[A] // error + | ^^^^^^^^^^^ + | precise type parameter A occurs in imprecise position in preciseTypeAliasExtendMorePrecise.FooCon[A] +-- Error: tests/neg/precise-alias-extends.scala:38:37 ------------------------------------------------------------------ +38 | opaque type FooConOpaque[@precise -A] = FooCon[A] // error + | ^^^^^^^^^^^ + | precise type parameter A occurs in imprecise position in preciseTypeAliasExtendMorePrecise.FooCon[A] +-- Error: tests/neg/precise-alias-extends.scala:44:21 ------------------------------------------------------------------ +44 | class FooInvExtend[A](val value : A) extends FooInv[A] // error + | ^ + | imprecise type parameter A occurs in precise position in preciseTypeAliasExtendLessPrecise.FooInv[A] +-- Error: tests/neg/precise-alias-extends.scala:46:27 ------------------------------------------------------------------ +46 | opaque type FooInvOpaque[A] = FooInv[A] // error + | ^ + | imprecise type parameter A occurs in precise position in preciseTypeAliasExtendLessPrecise.FooInv[A] +-- Error: tests/neg/precise-alias-extends.scala:50:22 ------------------------------------------------------------------ +50 | class FooCovExtend[+A](val value : A) extends FooCov[A] // error + | ^^ + | imprecise type parameter A occurs in precise position in preciseTypeAliasExtendLessPrecise.FooCov[A] +-- Error: tests/neg/precise-alias-extends.scala:52:28 ------------------------------------------------------------------ +52 | opaque type FooCovOpaque[+A] = FooCov[A] // error + | ^^ + | imprecise type parameter A occurs in precise position in preciseTypeAliasExtendLessPrecise.FooCov[A] +-- Error: tests/neg/precise-alias-extends.scala:55:22 ------------------------------------------------------------------ +55 | class FooConExtend[-A] extends FooCon[A] // error + | ^^ + | imprecise type parameter A occurs in precise position in preciseTypeAliasExtendLessPrecise.FooCon[A] +-- Error: tests/neg/precise-alias-extends.scala:57:28 ------------------------------------------------------------------ +57 | opaque type FooConOpaque[-A] = FooCon[A] // error + | ^^ + | imprecise type parameter A occurs in precise position in preciseTypeAliasExtendLessPrecise.FooCon[A] +-- Error: tests/neg/precise-alias-extends.scala:68:26 ------------------------------------------------------------------ +68 | type BoxAlias2[@precise A] = Foo[Box[A]] // error + | ^^^^^^^^^^ + | precise type parameter A occurs in imprecise position in preciseTypeAliasComposition.Box[A] +-- Error: tests/neg/precise-alias-extends.scala:78:16 ------------------------------------------------------------------ +78 | type Less[T] <: PBox[T] // error + | ^ + | imprecise type parameter T occurs in precise position in preciseTypeBounds.PBox[T] +-- Error: tests/neg/precise-alias-extends.scala:83:25 ------------------------------------------------------------------ +83 | type More[@precise T] >: Box[T] // error + | ^^^^^^^^^^ + | precise type parameter T occurs in imprecise position in preciseTypeBounds.Box[T] +-- Error: tests/neg/precise-alias-extends.scala:87:21 ------------------------------------------------------------------ +87 | opaque type Less[T] <: PBox[T] = PBox[T] // error + | ^ + | imprecise type parameter T occurs in precise position in preciseTypeBounds.PBox[T] +-- Error: tests/neg/precise-alias-extends.scala:88:30 ------------------------------------------------------------------ +88 | opaque type More[@precise T] <: Box[T] = Box[T] // error + | ^^^^^^^^^^ + | precise type parameter T occurs in imprecise position in preciseTypeBounds.Box[T] diff --git a/tests/neg/precise-alias-extends.scala b/tests/neg/precise-alias-extends.scala new file mode 100644 index 000000000000..b278a4353f50 --- /dev/null +++ b/tests/neg/precise-alias-extends.scala @@ -0,0 +1,93 @@ +import annotation.precise + +object preciseTypeAliasExtendSamePrecise: + trait FooInv[@precise T]: + val value: T + class FooInvExtend[@precise A](val value : A) extends FooInv[A] + type FooInvAlias[@precise A] = FooInv[A] + opaque type FooInvOpaque[@precise A] = FooInv[A] + + trait FooCov[@precise +T]: + val value: T + class FooCovExtend[@precise +A](val value : A) extends FooCov[A] + type FooCovAlias[@precise +A] = FooCov[A] + opaque type FooCovOpaque[@precise +A] = FooCov[A] + + trait FooCon[@precise -T] + class FooConExtend[@precise -A] extends FooCon[A] + type FooConAlias[@precise -A] = FooCon[A] + opaque type FooConOpaque[@precise -A] = FooCon[A] + + +object preciseTypeAliasExtendMorePrecise: + trait FooInv[T]: + val value: T + class FooInvExtend[@precise A](val value : A) extends FooInv[A] + type FooInvAlias[@precise A] = FooInv[A] // error + opaque type FooInvOpaque[@precise A] = FooInv[A] // error + + trait FooCov[+T]: + val value: T + class FooCovExtend[@precise +A](val value : A) extends FooCov[A] + type FooCovAlias[@precise +A] = FooCov[A] // error + opaque type FooCovOpaque[@precise +A] = FooCov[A] // error + + trait FooCon[-T] + class FooConExtend[@precise -A] extends FooCon[A] + type FooConAlias[@precise -A] = FooCon[A] // error + opaque type FooConOpaque[@precise -A] = FooCon[A] // error + + +object preciseTypeAliasExtendLessPrecise: + trait FooInv[@precise T]: + val value: T + class FooInvExtend[A](val value : A) extends FooInv[A] // error + type FooInvAlias[A] = FooInv[A] + opaque type FooInvOpaque[A] = FooInv[A] // error + + trait FooCov[@precise +T]: + val value: T + class FooCovExtend[+A](val value : A) extends FooCov[A] // error + type FooCovAlias[+A] = FooCov[A] + opaque type FooCovOpaque[+A] = FooCov[A] // error + + trait FooCon[@precise -T] + class FooConExtend[-A] extends FooCon[A] // error + type FooConAlias[-A] = FooCon[A] + opaque type FooConOpaque[-A] = FooCon[A] // error + + +object preciseTypeAliasComposition: + trait Foo[@precise T] + trait Box[T] + + type FooAlias1[A] = Box[Foo[A]] + type FooAlias2[A] = Foo[Box[A]] + + type BoxAlias1[@precise A] = Box[Foo[A]] + type BoxAlias2[@precise A] = Foo[Box[A]] // error + + +object preciseTypeBounds: + class Box[T] + class PBox[@precise T] + + object Alias: + object Upper: + type Same[@precise T] <: PBox[T] + type Less[T] <: PBox[T] // error + type More[@precise T] <: Box[T] + object Lower: + type Same[@precise T] >: PBox[T] + type Less[T] >: PBox[T] + type More[@precise T] >: Box[T] // error + + object Opaque: + opaque type Same[@precise T] <: PBox[T] = PBox[T] + opaque type Less[T] <: PBox[T] = PBox[T] // error + opaque type More[@precise T] <: Box[T] = Box[T] // error + + +object preciseGivenWith: + class PreciseBox[@precise T] + given [T]: PreciseBox[T] with {} \ No newline at end of file diff --git a/tests/neg/precise-override.check b/tests/neg/precise-override.check new file mode 100644 index 000000000000..a413c8766042 --- /dev/null +++ b/tests/neg/precise-override.check @@ -0,0 +1,24 @@ +-- [E163] Declaration Error: tests/neg/precise-override.scala:14:10 ---------------------------------------------------- +14 | def id[@precise T](t: T): T = ??? // error + | ^ + | error overriding method id in class Foo of type [T](t: T): T; + | method id of type [@precise T](t: T): T has incompatible type + | + | longer explanation available when compiling with `-explain` +-- [E163] Declaration Error: tests/neg/precise-override.scala:20:10 ---------------------------------------------------- +20 | def id[T](t: T): T = ??? // error + | ^ + | error overriding method id in class Foo of type [@precise T](t: T): T; + | method id of type [T](t: T): T has incompatible type + | + | longer explanation available when compiling with `-explain` +-- [E164] Declaration Error: tests/neg/precise-override.scala:34:12 ---------------------------------------------------- +34 | class Box[T] // error + | ^ + | error overriding type Box in trait Foo with bounds[T]; + | class Box has different precise type parameter annotations +-- [E164] Declaration Error: tests/neg/precise-override.scala:40:12 ---------------------------------------------------- +40 | class Box[@precise T] // error + | ^ + | error overriding type Box in trait Foo with bounds[T]; + | class Box has different precise type parameter annotations diff --git a/tests/neg/precise-override.scala b/tests/neg/precise-override.scala new file mode 100644 index 000000000000..88b432d57bda --- /dev/null +++ b/tests/neg/precise-override.scala @@ -0,0 +1,40 @@ +import annotation.precise + +object preciseDefOverride: + object SamePrecise: + abstract class Foo: + def id[@precise T](t: T) : T + class Bar extends Foo: + def id[@precise T](t: T): T = ??? + + object MorePrecise: + abstract class Foo: + def id[T](t: T) : T + class Bar extends Foo: + def id[@precise T](t: T): T = ??? // error + + object LessPrecise: + abstract class Foo: + def id[@precise T](t: T) : T + class Bar extends Foo: + def id[T](t: T): T = ??? // error + + +object preciseTypeAliasOverride: + object SamePrecise: + trait Foo: + type Box[@precise T] + class Bar extends Foo: + class Box[@precise T] + + object LessPrecise: + trait Foo: + type Box[@precise T] + class Bar extends Foo: + class Box[T] // error + + object MorePrecise: + trait Foo: + type Box[T] + class Bar extends Foo: + class Box[@precise T] // error \ No newline at end of file diff --git a/tests/neg/precise-typecheck.check b/tests/neg/precise-typecheck.check new file mode 100644 index 000000000000..6dce21f8f459 --- /dev/null +++ b/tests/neg/precise-typecheck.check @@ -0,0 +1,18 @@ +-- Error: tests/neg/precise-typecheck.scala:391:16 --------------------------------------------------------------------- +391 | val x = id(2L) // error + | ^ + | Cannot prove that (2L : Long) =:= (1L : Long). +-- [E007] Type Mismatch Error: tests/neg/precise-typecheck.scala:460:45 ------------------------------------------------ +460 | val id1Check: Id1 = [T] => (t: T) => Box(t) // error + | ^ + | Found: [T] => (t: T) => Box[T] + | Required: precisePolymorphicTypesAndValues.Id1 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/precise-typecheck.scala:463:54 ------------------------------------------------ +463 | val id2Check: Id2 = [@precise T] => (t: T) => Box(t) // error + | ^ + | Found: [@precise T] => (t: T) => Box[T] + | Required: precisePolymorphicTypesAndValues.Id2 + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/precise-typecheck.scala b/tests/neg/precise-typecheck.scala new file mode 100644 index 000000000000..c8669e0b4c13 --- /dev/null +++ b/tests/neg/precise-typecheck.scala @@ -0,0 +1,489 @@ +import annotation.{precise, targetName} +import annotation.unchecked.uncheckedVariance +import language.implicitConversions + +class Box[T](x: T) + +object preciseDefs: + def id[@precise T](x: T): T = x + def idBox[@precise T](t: T): Box[T] = ??? + + final val x = id(3) + val xTest: 3 = x + + final val x3 = id(id(id(3))) + val x3Test: 3 = x3 + + final val tpl = id((1, 2)) + val tplTest: (1, 2) = tpl + + final val tpl2 = id((1, 2, (3, 4))) + val tpl2Test: (1, 2, (3, 4)) = tpl2 + + final val list1 = id(1 :: Nil) + val list1Test: List[1] = list1 + + final val list1hi = id(1 :: "hi" :: Nil) + val list1hiTest: List[1 | "hi"] = list1hi + + val c: Boolean = ??? + val ifVal = idBox(if c then 2 else 3) + val ifValTest: Box[2 | 3] = ifVal + + def huge[@precise T1, T2, T3, @precise T4, @precise T5]( + t1: T1, t2: T2, r: Int + )(t3: T3, r2: Int, t4: T4)(t5: T5*): Box[(T1, T2, T3, T4, T5)] = ??? + + val h1 = huge((1, 2), (3, 4), 5)((6, 7), 8, (9, 10))(11, 12) + val h1Test: Box[((1, 2), (Int, Int), (Int, Int), (9, 10), 11 | 12)] = h1 + + +object preciseUpperBound: + def id[@precise TP, T <: TP](t: T): Box[T] = ??? + val x = id(1) + val xTest: Box[1] = x + + +object preciseDepArgs: + def same[@precise T](x: T, y: T): Box[T] = ??? + final val sameVal = same(1, 2) + val sameValTest: Box[1 | 2] = sameVal + + def sameVarArgs[@precise T](a: T*): Box[T] = ??? + final val sameVal123hi = sameVarArgs(1, 2, 3, "hi") + val sameVal123hiTest: Box[1 | 2 | 3 | "hi"] = sameVal123hi + + def dep2[T1, @precise T2 <: T1](t1: T1)(t2: T2): Box[T1] = ??? + final val d2 = dep2(1)(2) + val d2Test: Box[Int] = d2 + +// TODO: revisit after fix for https://github.com/lampepfl/dotty/issues/15813 +// def dep1[@precise T1, T2 <: T1](t1: T1)(t2: T2): Box[T1] = ??? +// val d1 = dep1(1)(2) + +// TODO: revisit after fix for https://github.com/lampepfl/dotty/issues/15813 +// def dep12[@precise T1, @precise T2 <: T1](t1: T1, t2: T2): Box[T1] = ??? +// final val d12 = dep12(1)(2) +// val d12Test: Box[1 | 2] = d12 + + +object preciseExtendClassArgs: + class Foo[@precise T](val value: T) + + object Bar extends Foo(5) + val barTest: 5 = Bar.value + + object Baz extends Foo((1, 2)) + val bazTest: (1, 2) = Baz.value + + +object preciseEnums: + enum Foo[@precise T](val value: T): + case Bar extends Foo(5) + case Baz extends Foo((1, 2)) + + import Foo.* + val barTest: 5 = Bar.value + val bazTest: (1, 2) = Baz.value + + +object preciseDefaultValues: + def id[@precise T](x: T = 1): Box[T] = ??? + def np[T](x: T = 1): Box[T] = ??? + + val x = id() + val xTest: Box[1] = x + val y = np() + val yTest: Box[Int] = y + + def idTpl[@precise T](x: Int)(y: T = (1, (2, 3))): Box[T] = ??? + def npTpl[T](x: Int)(y: T = (1, (2, 3))): Box[T] = ??? + + val xTpl = idTpl(22)() + val xTplTest: Box[(1, (2, 3))] = xTpl + val yTpl = npTpl(22)() + val yTplTest: Box[(Int, (Int, Int))] = yTpl + + +object preciseGivens: + given one: 1 = 1 + def fetch[@precise T <: Int](using T): Box[T] = ??? + val f = fetch + val fTest: Box[1] = f + + class PreciseBox[@precise T] + given [T]: PreciseBox[T] with {} + + def check[T](t: T)(using PreciseBox[T]): Box[T] = ??? + val c1 = check(1) + val c1Test: Box[Int] = c1 + + def foo[@precise T: Box](arg: T): Box[T] = summon[Box[T]] + object fooTest: + given Box[1] = new Box[1](1) + val x = foo(1) + val xTest: Box[1] = x + + +object preciseTypeParamPropagation: + def idBox[@precise T](t: T): Box[T] = ??? + def idBoxBox[BB](x: Box[BB]): Box[BB] = x + val bb1 = idBoxBox(idBox(1)) + val bb1Test: Box[1] = bb1 + + +object preciseInvariance: + class PreciseBox[@precise T](x: T) + val c: Boolean = ??? + val b = PreciseBox(if c then 2 else 3) + val bTest: PreciseBox[2 | 3] = b + final val b3 = PreciseBox(PreciseBox(PreciseBox(3))) + val b3Test: PreciseBox[PreciseBox[PreciseBox[3]]] = b3 + + final val tpl = PreciseBox((1, (2, 3), "hi")) + val tplTest: PreciseBox[(1, (2, 3), "hi")] = tpl + + final val tpl2: (1, (2, 3), "hi") = (1, (2, 3), "hi") + final val tpl3 = PreciseBox(tpl2) + val tpl3Test: PreciseBox[tpl2.type] = tpl3 + + class Boxx[@precise T](x: T*) + val b123 = Boxx(1, 2, 3) + val b123Test: Boxx[1 | 2 | 3] = b123 + + +object preciseCovariance: + case class BoxC[@precise +A](x: A) + def fromBox[B <: Any](x: BoxC[B]): BoxC[B] = x + final val b1 = BoxC(1) + val b11 = fromBox(b1) + val b11Test: BoxC[1] = b11 + val b11CovTest: BoxC[Int] = b11 + + class Inv[A, B] + class BoxCI[@precise +C, +I](c: C, i: I) + def fromBoxCI[C, I](x: BoxCI[C, I]): Inv[C, I] = ??? + val bci = BoxCI(1, 2) + val bciTest: BoxCI[1, Int] = bci + val fbci = fromBoxCI(bci) + val fbciTest: Inv[1, Int] = fbci + val fbci12 = fromBoxCI(??? : BoxCI[1, 2]) + val fbci12Test: Inv[1, Int] = fbci12 + + class BoxIC[+I, @precise +C](i: I, c: C) + def fromBoxIC[I, C](x: BoxIC[I, C]): Inv[I, C] = ??? + val bic = BoxIC(1, 2) + val bicTest: BoxIC[Int, 2] = bic + val fbic = fromBoxIC(bic) + val fbicTest: Inv[Int, 2] = fbic + val fbic12 = fromBoxIC(??? : BoxIC[1, 2]) + val fbic12Test: Inv[Int, 2] = fbic12 + + +object preciseTypeAlias: + case class BoxC[@precise +A](x: A) + type BoxCC[A] = BoxC[A] + def fromBox[B](x: BoxCC[B]): BoxCC[B] = x + final val b1 = BoxC(1) + val b11 = fromBox(b1) + val b11Test: BoxCC[1] = b11 + val b11CovTest: BoxCC[Int] = b11 + + +object preciseCovariantComposition: + object direct: + class BoxC[@precise +A] + def fromBox[B](x: Box[BoxC[B]]): BoxC[B] = ??? + val bb1: Box[BoxC[1]] = ??? + val frombb1 = fromBox(bb1) + val frombb1Test: BoxC[1] = frombb1 + + object boring: + class BoxC[@precise +A] + def fromBox[B](x: BoxC[(1, B)]): BoxC[B] = ??? + val b1: BoxC[(1, 1)] = ??? + val fromb1 = fromBox(b1) + val fromb1Test: BoxC[1] = fromb1 + + +object preciseCovariantOpaque: + opaque type BoxC[@precise +A] = A + def fromBox[B <: Any](x: BoxC[B]): BoxC[B] = x + val b1: BoxC[1] = ??? + val b11 = fromBox(b1) + val b11Test: BoxC[1] = b11 + + +object preciseContravariantGiven: + object directPreciseInTC: + trait TC[@precise -T]: + type Out = Box[T @uncheckedVariance] + val value: Out = ??? + object TC: + given [T]: TC[T] = new TC[T]{} + + val b1 = summon[TC[1]] + val b1Test: Box[1] = b1.value + + object directPreciseInGiven: + trait TC[-T]: + type Out = Box[T @uncheckedVariance] + val value: Out = ??? + + object TC: + given fromInt[@precise T <: Int](using DummyImplicit): TC[T] = new TC[T] {} + given fromString[T <: String]: TC[T] = new TC[T] {} + + val b1 = summon[TC[1]] + val b1Test: Box[1] = b1.value + val bHi = summon[TC["Hi"]] + val bHiTest: Box[String] = bHi.value + + object secondaryGivens: + trait TC2[@precise -T]: + type Out = Box[T @uncheckedVariance] + val value: Out = ??? + object TC2: + given [T]: TC2[T] = new TC2[T] {} + trait TC[-T]: + type Out + val value: Out = ??? + object TC: + transparent inline given fromInt[@precise T <: Int](using tc2: TC2[T]): TC[T] = new TC[T]: + type Out = tc2.Out + + val b1 = summon[TC[1]] + val b1Test: Box[1] = b1.value + + object covariant: + trait AbstractBox + class BoxC[@precise +T] extends AbstractBox + trait TC[-T]: + type Out <: AbstractBox + val value: Out = ??? + object TC: + transparent inline given [T]: TC[BoxC[T]] = new TC[BoxC[T]]: + type Out = BoxC[T] + + val b1 = summon[TC[BoxC[1]]] + val b1Test: BoxC[1] = b1.value + + +object preciseExtDefs: + object form1: + extension [@precise T, V](t: T) + def tester(v: V): Box[(T, V)] = ??? + def tester: Box[T] = ??? + + val x = (1, 2).tester(3) + val xTest: Box[((1, 2), Int)] = x + + val y = (1, 2).tester + val yTest: Box[(1, 2)] = y + + object form2: + extension[@precise T, @precise V] (t: T) + def tester(v: V): Box[(T, V)] = ??? + def tester: Box[T] = ??? + + val x = (1, 2).tester(3) + val xTest: Box[((1, 2), 3)] = x + + val y = (1, 2).tester + val yTest: Box[(1, 2)] = y + + object form3: + extension[T, @precise V] (t: T) + def tester(v: V): Box[(T, V)] = ??? + def tester: Box[T] = ??? + + val x = (1, 2).tester(3) + val xTest: Box[((Int, Int), 3)] = x + + val y = (1, 2).tester + val yTest: Box[(Int, Int)] = y + + object form4: + extension [@precise T](t: T) + def testerp[@precise V](v: V): Box[(T, V)] = ??? + def testernp[V](v: V): Box[(T, V)] = ??? + + val x = (1, 2).testerp(3) + val xTest: Box[((1, 2), 3)] = x + + val y = (1, 2).testernp(3) + val yTest: Box[((1, 2), Int)] = y + + +object preciseImplicitClasses: + object form1: + implicit class Ext[@precise T](t: T): + def tester[@precise V](v: V): Box[(T, V)] = ??? + def tester: Box[T] = ??? + + val x = (1, 2).tester(3) + val xTest: Box[((1, 2), 3)] = x + + val y = (1, 2).tester + val yTest: Box[(1, 2)] = y + + object form2: + implicit class Ext[@precise T](t: T): + def tester[V](v: V): Box[(T, V)] = ??? + + val x = (1, 2).tester(3) + val xTest: Box[((1, 2), Int)] = x + + object form3: + implicit class Ext[T](t: T): + def tester[@precise V](v: V): Box[(T, V)] = ??? + + val x = (1, 2).tester(3) + val xTest: Box[((Int, Int), 3)] = x + + +object preciseCovarianceWithCompiletimeOps: + import compiletime.ops.int.+ + class Inlined[@precise +T <: Int] + extension [T <: Int](lhs: Inlined[T]) + def inc: Inlined[T + 1] = ??? + + val i1 = Inlined[1] + val i3: Inlined[3] = i1.inc.inc + + +object preciseByName: + def id[@precise T](t: => T): Box[T] = ??? + val x = id(1) + val xTest: Box[1] = x + val y = id((1, 2)) + val yTest: Box[(1, 2)] = y + + +object preciseFuncX: + object func0: + def id[@precise T](t: () => T): Box[T] = ??? + val x = id(() => 1) + val xTest: Box[1] = x + val y = id(() => (1, 2)) + val yTest: Box[(1, 2)] = y + + object func2: + def id[@precise T](t: (Int, Int) => T): Box[T] = ??? + val y = id((a, b) => (1, 2)) + val yTest: Box[(1, 2)] = y + + +object preciseOverloading: + @targetName("intID") + def id[@precise T <: Int](t: T): Box[T] = ??? + @targetName("stringID") + def id[T <: String](t: T): Box[T] = ??? + @targetName("longID") + def id[@precise T <: Long](t: T)(using T =:= 1L): Box[T] = ??? + + val i1 = id(1) + val i1Test: Box[1] = i1 + val iOne = id("One") + val iOneTest: Box[String] = iOne + val i1L = id(1L) + val i1LTest: Box[1L] = i1L + val x = id(2L) // error + + class Foo: + def ==[@precise R](r: R): Box[R] = ??? + + val f = new Foo + val x = f == ((1, 2), 3) + val xTest: Box[((1, 2), 3)] = x + + +object preciseImplicitConversion: + object normalDefs: + implicit def toBoxFromTuple[@precise T <: Tuple](from: T): Box[T] = Box(from) + implicit def toBoxFromInt[@precise T <: Int](from: T): Box[T] = Box(from) + implicit def toBoxFromString[T <: String](from: T): Box[T] = Box(from) + def box[T1, T2, T3, @precise T4, T5]( + b1: Box[T1], b2: Box[T2], r: Int, b3: Box[T3] + )( + r2: Int, b4: Box[T4], b5: Box[T5] + ): Box[(T1, T2, T3, T4, T5)] = ??? + + final val b1 = box((1, 2), (3, 4), 999, 4)(999, "two", 22) + val b1Test: Box[((1, 2), (3, 4), 4, "two", 22)] = b1 + + final val b2 = box("one", (1, 2), 999, 3)(999, ("hi", "there"), "bye") + val b2Test: Box[(String, (1, 2), 3, ("hi", "there"), String)] = b2 + + object transparentInline1: + trait OutBox[T]{ type Out } + object OutBox: + transparent inline implicit def conv[T, @precise V](value: V): OutBox[T] = + new OutBox[T]{ type Out = V } + + def out[T](ob: OutBox[T]): ob.Out = ??? + + val x = out((0, 1)) + val xTest: (0, 1) = x + + object transparentInline2: + trait OutBox[T]{ type Out } + object OutBox: + transparent inline implicit def conv[T, @precise V](inline value: V): OutBox[T] = + new OutBox[T]{ type Out = V } + + def out[T](ob: OutBox[T]): ob.Out = ??? + + val x = out((0, 1)) + val xTest: (0, 1) = x + + +object preciseImplicitConversionNewStyle: + given toBoxFromTuple[@precise T <: Tuple]: Conversion[T, Box[T]] = Box(_) + given toBoxFromInt[@precise T <: Int]: Conversion[T, Box[T]] = Box(_) + given toBoxFromString[T <: String]: Conversion[T, Box[T]] = Box(_) + def box[T1, T2, T3, @precise T4, T5]( + b1: Box[T1], b2: Box[T2], r: Int, b3: Box[T3] + )( + r2: Int, b4: Box[T4], b5: Box[T5] + ): Box[(T1, T2, T3, T4, T5)] = ??? + + final val b1 = box((1, 2), (3, 4), 999, 4)(999, "two", 22) + val b1Test: Box[((1, 2), (3, 4), 4, "two", 22)] = b1 + + final val b2 = box("one", (1, 2), 999, 3)(999, ("hi", "there"), "bye") + val b2Test: Box[(String, (1, 2), 3, ("hi", "there"), String)] = b2 + + +object precisePolymorphicTypesAndValues: + type Id1 = [@precise T] => T => Box[T] + val id1Check: Id1 = [T] => (t: T) => Box(t) // error + + type Id2 = [T] => T => Box[T] + val id2Check: Id2 = [@precise T] => (t: T) => Box(t) // error + + +object preciseBlock: + def id[@precise T](x: T): Box[T] = ??? + def np[T](x: T): Box[T] = ??? + + val x = id({ + val y = id(1) + val yTest: Box[1] = y + 1 + }) + val xTest: Box[1] = x + + val x2 = id({ + val y = np(1) + val yTest: Box[Int] = y + 1 + }) + val x2Test: Box[1] = x2 + + +object preciseTupleParams: + object p_p: + def id[@precise TT1, @precise TT2](t: (TT1, TT2)): (TT1, TT2) = ??? + val x = id(((0, 1), 2)) + val xTest: ((0, 1), 2) = x diff --git a/tests/pos/precise-tc/precise-lib_1.scala b/tests/pos/precise-tc/precise-lib_1.scala new file mode 100644 index 000000000000..6703fe73a046 --- /dev/null +++ b/tests/pos/precise-tc/precise-lib_1.scala @@ -0,0 +1,28 @@ +import scala.annotation.precise +import scala.annotation.unchecked.uncheckedVariance + +object preciseLib: + sealed trait Args + sealed trait NoArgs extends Args + sealed trait Args2[T1, T2] extends Args + trait IRDFType + trait IRDFVector extends IRDFType + trait IRDFBool extends IRDFType + sealed trait DFError + class DFType[+T <: IRDFType, +A <: Args] + type DFTypeAny = DFType[IRDFType, Args] + object DFBool extends DFType[IRDFBool, NoArgs] + type DFBool = DFBool.type + type DFVector[+VT <: DFTypeAny, +VD <: NonEmptyTuple] = + DFType[IRDFVector, Args2[VT @uncheckedVariance, VD @uncheckedVariance]] + + trait TC[@precise -TCT1]: + type Type <: DFTypeAny + object TC: + transparent inline given ofDFType[TCT2 <: DFTypeAny]: TC[TCT2] = new TC[TCT2]: + type Type = TCT2 + + extension [@precise EXT, @precise EXD <: Int](t: EXT)(using tc: TC[EXT]) + def XX( + cellDim: EXD + ): DFVector[tc.Type, Tuple1[EXD]] = ??? \ No newline at end of file diff --git a/tests/pos/precise-tc/precise-test_2.scala b/tests/pos/precise-tc/precise-test_2.scala new file mode 100644 index 000000000000..f3fb494f0a0e --- /dev/null +++ b/tests/pos/precise-tc/precise-test_2.scala @@ -0,0 +1,4 @@ +import preciseLib.* + +object check: + val x: DFVector[DFVector[DFBool, Tuple1[777]], Tuple1[888]] = DFBool XX 777 XX 888 \ No newline at end of file From 23b4cb41e5532ab86b54ad97e349c4e976cf581e Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 16:01:38 -0400 Subject: [PATCH 02/17] add `Precise` Mode --- compiler/src/dotty/tools/dotc/core/Mode.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index d141cf7032ee..5b439f59bc96 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -11,7 +11,7 @@ case class Mode(val bits: Int) extends AnyVal { def isExpr: Boolean = (this & PatternOrTypeBits) == None override def toString: String = - (0 until 31).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")") + (0 until 32).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")") def ==(that: Mode): Boolean = this.bits == that.bits def !=(that: Mode): Boolean = this.bits != that.bits @@ -129,4 +129,9 @@ object Mode { * Type `Null` becomes a subtype of non-primitive value types in TypeComparer. */ val RelaxedOverriding: Mode = newMode(30, "RelaxedOverriding") + + /** + * Indication that argument widening should not take place. + */ + val Precise: Mode = newMode(31, "Precise") } From 7bb68cf480bc0e61540360c2b7afaf702a38957a Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 16:16:54 -0400 Subject: [PATCH 03/17] add `scala.annotation.precise` --- .../src/dotty/tools/dotc/core/Definitions.scala | 1 + library/src/scala/annotation/precise.scala | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 library/src/scala/annotation/precise.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 83d945352321..0d1300b6d4a5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -988,6 +988,7 @@ class Definitions { @tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn") @tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait") @tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native") + @tu lazy val PreciseAnnot: ClassSymbol = requiredClass("scala.annotation.precise") @tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated") @tu lazy val SourceFileAnnot: ClassSymbol = requiredClass("scala.annotation.internal.SourceFile") @tu lazy val ScalaSignatureAnnot: ClassSymbol = requiredClass("scala.reflect.ScalaSignature") diff --git a/library/src/scala/annotation/precise.scala b/library/src/scala/annotation/precise.scala new file mode 100644 index 000000000000..ae8a79636bb1 --- /dev/null +++ b/library/src/scala/annotation/precise.scala @@ -0,0 +1,14 @@ +package scala.annotation + +import scala.annotation.Annotation + +/** + * This annotation is applicable on type parameter declarations and instructs the compiler to + * make best-effort in keeping the annotated parameter as precise as possible. + * This feature is currently experimental. See SIP for documentation. + * TODO: complete documentation reference here after SIP is approved. + */ +@experimental +class precise extends Annotation { + +} From b7c6326d20d22af30506c1f9ea8ce87fbc6e2fa7 Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 16:19:49 -0400 Subject: [PATCH 04/17] add `precise` to `Symbol` add `paramPrecise` to `ParamInfo` add `paramPrecises` to LambdaType and propagate it through all dependent code that construct new lambdas (poly, method, higher-kinded), and including the user-facing macro quotes API --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- .../dotty/tools/dotc/core/Definitions.scala | 6 +- .../dotty/tools/dotc/core/Denotations.scala | 3 +- .../tools/dotc/core/GadtConstraint.scala | 2 +- .../tools/dotc/core/OrderingConstraint.scala | 2 +- .../src/dotty/tools/dotc/core/ParamInfo.scala | 3 + .../tools/dotc/core/SymDenotations.scala | 5 ++ .../src/dotty/tools/dotc/core/Symbols.scala | 6 ++ .../dotty/tools/dotc/core/TypeComparer.scala | 3 +- .../dotty/tools/dotc/core/TypeErasure.scala | 6 +- .../src/dotty/tools/dotc/core/Types.scala | 87 +++++++++++-------- .../dotc/core/classfile/ClassfileParser.scala | 8 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../dotc/inlines/PrepareInlineable.scala | 2 +- .../dotc/transform/CompleteJavaEnums.scala | 1 + .../dotc/transform/ElimErasedValueType.scala | 2 +- .../tools/dotc/transform/ElimRepeated.scala | 10 +-- .../tools/dotc/transform/ExplicitOuter.scala | 2 +- .../dotc/transform/FullParameterization.scala | 7 +- .../dotc/transform/sjs/PrepJSExports.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 14 +-- .../src/dotty/tools/dotc/typer/Deriving.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 1 + .../dotty/tools/dotc/typer/ProtoTypes.scala | 4 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 +- .../quoted/runtime/impl/QuotesImpl.scala | 11 ++- .../tools/dotc/StringFormatterTest.scala | 2 +- library/src/scala/quoted/Quotes.scala | 4 + project/MiMaFilters.scala | 6 ++ .../stdlibExperimentalDefinitions.scala | 5 ++ 30 files changed, 134 insertions(+), 80 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index a3e88699e424..ebdfdb3e7ebf 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -405,7 +405,7 @@ extends tpd.TreeTraverser: info match case mt: MethodOrPoly => val psyms = psymss.head - mt.companion(mt.paramNames)( + mt.companion(mt.paramNames, mt.paramPrecises)( mt1 => if !psyms.exists(_.isUpdatedAfter(preRecheckPhase)) && !mt.isParamDependent && prevLambdas.isEmpty then mt.paramInfos diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0d1300b6d4a5..107bee296594 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -133,7 +133,7 @@ class Definitions { val underlyingName = name.asSimpleName.drop(6) val underlyingClass = ScalaPackageVal.requiredClass(underlyingName) denot.info = TypeAlias( - HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)( + HKTypeLambda(argParamNames :+ "R".toTypeName, Nil, argVariances :+ Covariant)( tl => List.fill(arity + 1)(TypeBounds.empty), tl => CapturingType(underlyingClass.typeRef.appliedTo(tl.paramRefs), CaptureSet.universal) @@ -187,7 +187,7 @@ class Definitions { useCompleter: Boolean = false) = { val tparamNames = PolyType.syntheticParamNames(typeParamCount) val tparamInfos = tparamNames map (_ => bounds) - def ptype = PolyType(tparamNames)(_ => tparamInfos, resultTypeFn) + def ptype = PolyType(tparamNames, Nil)(_ => tparamInfos, resultTypeFn) val info = if (useCompleter) new LazyType { @@ -719,7 +719,7 @@ class Definitions { case meth: MethodType => info.derivedLambdaType( resType = meth.derivedLambdaType( - paramNames = Nil, paramInfos = Nil)) + paramNames = Nil, paramPrecises = Nil, paramInfos = Nil)) } } val argConstr = constr.copy().entered diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 61dd71603f64..42a9d086caf6 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -546,7 +546,7 @@ object Denotations { && tp1.isErasedMethod == tp2.isErasedMethod => val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection) if resType.exists then - tp1.derivedLambdaType(mergeParamNames(tp1, tp2), tp1.paramInfos, resType) + tp1.derivedLambdaType(mergeParamNames(tp1, tp2), Nil, tp1.paramInfos, resType) else NoType case _ => NoType case tp1: PolyType => @@ -556,6 +556,7 @@ object Denotations { if resType.exists then tp1.derivedLambdaType( mergeParamNames(tp1, tp2), + Nil, tp1.paramInfos.zipWithConserve(tp2.paramInfos)( _ & _ ), resType) else NoType diff --git a/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala b/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala index d8e1c5276ab6..0ad055de1f4b 100644 --- a/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala @@ -91,7 +91,7 @@ final class ProperGadtConstraint private( override def addToConstraint(params: List[Symbol])(using Context): Boolean = { import NameKinds.DepParamName - val poly1 = PolyType(params.map { sym => DepParamName.fresh(sym.name.toTypeName) })( + val poly1 = PolyType(params.map { sym => DepParamName.fresh(sym.name.toTypeName) }, params.map(_.paramPrecise))( pt => params.map { param => // In bound type `tp`, replace the symbols in dependent positions with their internal TypeParamRefs. // The replaced symbols will be later picked up in `ConstraintHandling#addToConstraint` diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 1341fac7d735..82651673642e 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -540,7 +540,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos: @unchecked paramInfos = TypeBounds(lo, LazyRef.of(hi)) :: pinfos1 } - ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType)) + ensureFresh(tl.newLikeThis(tl.paramNames, tl.paramPrecises, paramInfos, tl.resultType)) } else tl diff --git a/compiler/src/dotty/tools/dotc/core/ParamInfo.scala b/compiler/src/dotty/tools/dotc/core/ParamInfo.scala index e88d6540e64b..36dd42a9201f 100644 --- a/compiler/src/dotty/tools/dotc/core/ParamInfo.scala +++ b/compiler/src/dotty/tools/dotc/core/ParamInfo.scala @@ -38,6 +38,9 @@ trait ParamInfo { /** The variance of the type parameter */ def paramVariance(using Context): Variance + /** The precise enforcement indicator of the type parameter */ + def paramPrecise(using Context): Boolean + /** The variance of the type parameter, as a number -1, 0, +1. * Bivariant is mapped to 1, i.e. it is treated like Covariant. */ diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 66a1e44622b8..ed0991c01638 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1491,6 +1491,11 @@ object SymDenotations { else if is(Contravariant) then Contravariant else EmptyFlags + /** The precise enforcement indicator of this type parameter or type member + */ + final def precise(using Context): Boolean = + hasAnnotation(defn.PreciseAnnot) + /** The flags to be used for a type parameter owned by this symbol. * Overridden by ClassDenotation. */ diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 73fbcca6f6ed..dafc7596488c 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -346,6 +346,12 @@ object Symbols { def paramInfoAsSeenFrom(pre: Type)(using Context): Type = pre.memberInfo(this) def paramInfoOrCompleter(using Context): Type = denot.infoOrCompleter def paramVariance(using Context): Variance = denot.variance + def paramPrecise(using Context): Boolean = + val owner = denot.owner + if (owner.isConstructor) + owner.owner.typeParams.exists(p => p.name == name && p.paramPrecise) + else + denot.precise def paramRef(using Context): TypeRef = denot.typeRef // -------- Printing -------------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index ef6b12f3c6d4..b85f72424566 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1097,7 +1097,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling variancesConform(remainingTparams, tparams) && { val adaptedTycon = if d > 0 then - HKTypeLambda(remainingTparams.map(_.paramName))( + HKTypeLambda(remainingTparams.map(_.paramName), remainingTparams.map(_.paramPrecise))( tl => remainingTparams.map(remainingTparam => tl.integrate(remainingTparams, remainingTparam.paramInfo).bounds), tl => otherTycon.appliedTo( @@ -2456,6 +2456,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling else if (tparams1.hasSameLengthAs(tparams2)) HKTypeLambda( paramNames = HKTypeLambda.syntheticParamNames(tparams1.length), + paramPrecises = Nil, variances = if tp1.isDeclaredVarianceLambda && tp2.isDeclaredVarianceLambda then tparams1.lazyZip(tparams2).map((p1, p2) => combineVariance(p1.paramVariance, p2.paramVariance)) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 1fc7ee5d22a8..e0ba30378b43 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -234,7 +234,7 @@ object TypeErasure { def eraseParamBounds(tp: PolyType): Type = tp.derivedLambdaType( - tp.paramNames, tp.paramNames map (Function.const(TypeBounds.upper(defn.ObjectType))), tp.resultType) + tp.paramNames, tp.paramPrecises, tp.paramNames map (Function.const(TypeBounds.upper(defn.ObjectType))), tp.resultType) if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType]) else if (sym.isAbstractType) TypeAlias(WildcardType) @@ -642,14 +642,14 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst val formals = formals0.mapConserve(paramErasure) eraseResult(tp.resultType) match { case rt: MethodType => - tp.derivedLambdaType(names ++ rt.paramNames, formals ++ rt.paramInfos, rt.resultType) + tp.derivedLambdaType(names ++ rt.paramNames, Nil, formals ++ rt.paramInfos, rt.resultType) case NoType => // Can happen if we smuggle in a Nothing in the qualifier. Normally we prevent that // in Checking.checkMembersOK, but compiler-generated code can bypass this test. // See i15377.scala for a test case. NoType case rt => - tp.derivedLambdaType(names, formals, rt) + tp.derivedLambdaType(names, Nil, formals, rt) } case tp: PolyType => this(tp.resultType) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index bc00897d7783..043733d64802 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -804,7 +804,7 @@ object Types { def goApplied(tp: AppliedType, tycon: HKTypeLambda) = go(tycon.resType).mapInfo(info => - tycon.derivedLambdaAbstraction(tycon.paramNames, tycon.paramInfos, info).appliedTo(tp.args)) + tycon.derivedLambdaAbstraction(tycon.paramNames, tycon.paramPrecises, tycon.paramInfos, info).appliedTo(tp.args)) def goThis(tp: ThisType) = val underlying = tp.underlying @@ -3560,6 +3560,7 @@ object Types { def paramNames: List[ThisName] def paramInfos: List[PInfo] + def paramPrecises(using Context): List[Boolean] def resType: Type protected def newParamRef(n: Int): ParamRefType @@ -3605,19 +3606,20 @@ object Types { case params: List[Symbol @unchecked] => tp.subst(params, paramRefs) } - final def derivedLambdaType(paramNames: List[ThisName] = this.paramNames, + final def derivedLambdaType(using Context)(paramNames: List[ThisName] = this.paramNames, + paramPrecises: List[Boolean] = this.paramPrecises, paramInfos: List[PInfo] = this.paramInfos, - resType: Type = this.resType)(using Context): LambdaType = - if ((paramNames eq this.paramNames) && (paramInfos eq this.paramInfos) && (resType eq this.resType)) this - else newLikeThis(paramNames, paramInfos, resType) + resType: Type = this.resType): LambdaType = + if ((paramNames eq this.paramNames) && (paramPrecises eq this.paramPrecises) && (paramInfos eq this.paramInfos) && (resType eq this.resType)) this + else newLikeThis(paramNames, paramPrecises, paramInfos, resType) - def newLikeThis(paramNames: List[ThisName], paramInfos: List[PInfo], resType: Type)(using Context): This = + def newLikeThis(paramNames: List[ThisName], paramPrecises: List[Boolean], paramInfos: List[PInfo], resType: Type)(using Context): This = def substParams(pinfos: List[PInfo], to: This): List[PInfo] = pinfos match case pinfos @ (pinfo :: rest) => pinfos.derivedCons(pinfo.subst(this, to).asInstanceOf[PInfo], substParams(rest, to)) case nil => nil - companion(paramNames)( + companion(paramNames, paramPrecises)( x => substParams(paramInfos, x), x => resType.subst(this, x)) @@ -3871,6 +3873,7 @@ object Types { val paramInfos: List[Type] = paramInfosExp(this: @unchecked) val resType: Type = resultTypeExp(this: @unchecked) + def paramPrecises(using Context): List[Boolean] = Nil assert(resType.exists) def companion: MethodTypeCompanion @@ -3901,19 +3904,19 @@ object Types { memoizedNames.getOrElseUpdate(n, (0 until n).map(syntheticParamName).toList) } - def apply(paramNames: List[N])(paramInfosExp: LT => List[PInfo], resultTypeExp: LT => Type)(using Context): LT - def apply(paramNames: List[N], paramInfos: List[PInfo], resultType: Type)(using Context): LT = - apply(paramNames)(_ => paramInfos, _ => resultType) + def apply(paramNames: List[N], paramPrecises: List[Boolean])(paramInfosExp: LT => List[PInfo], resultTypeExp: LT => Type)(using Context): LT + def apply(paramNames: List[N], paramPrecises: List[Boolean], paramInfos: List[PInfo], resultType: Type)(using Context): LT = + apply(paramNames, paramPrecises)(_ => paramInfos, _ => resultType) def apply(paramInfos: List[PInfo])(resultTypeExp: LT => Type)(using Context): LT = - apply(syntheticParamNames(paramInfos.length))(_ => paramInfos, resultTypeExp) + apply(syntheticParamNames(paramInfos.length), Nil)(_ => paramInfos, resultTypeExp) def apply(paramInfos: List[PInfo], resultType: Type)(using Context): LT = - apply(syntheticParamNames(paramInfos.length), paramInfos, resultType) + apply(syntheticParamNames(paramInfos.length), Nil, paramInfos, resultType) protected def toPInfo(tp: Type)(using Context): PInfo def fromParams[PI <: ParamInfo.Of[N]](params: List[PI], resultType: Type)(using Context): Type = if (params.isEmpty) resultType - else apply(params.map(_.paramName))( + else apply(params.map(_.paramName), params.map(_.paramPrecise))( tl => params.map(param => toPInfo(tl.integrate(params, param.paramInfo))), tl => tl.integrate(params, resultType)) } @@ -3922,6 +3925,8 @@ object Types { extends LambdaTypeCompanion[TermName, Type, LT] { def toPInfo(tp: Type)(using Context): Type = tp def syntheticParamName(n: Int): TermName = nme.syntheticParamName(n) + def apply(paramNames: List[TermName], paramInfos: List[Type], resultType: Type)(using Context): LT = + apply(paramNames, Nil)(_ => paramInfos, _ => resultType) } abstract class TypeLambdaCompanion[LT <: TypeLambda] @@ -3961,7 +3966,7 @@ object Types { tl => tl.integrate(params, resultType)) } - final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = + final def apply(paramNames: List[TermName], paramPrecises: List[Boolean] = Nil)(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) def checkValid(mt: MethodType)(using Context): mt.type = { @@ -4007,19 +4012,24 @@ object Types { def newParamRef(n: Int): TypeParamRef = new TypeParamRefImpl(this, n) + val declaredParamPrecises: List[Boolean] + def paramPrecises(using Context): List[Boolean] = + if declaredParamPrecises.isEmpty then paramNames.map(_ => false) + else declaredParamPrecises + @threadUnsafe lazy val typeParams: List[LambdaParam] = paramNames.indices.toList.map(new LambdaParam(this, _)) - def derivedLambdaAbstraction(paramNames: List[TypeName], paramInfos: List[TypeBounds], resType: Type)(using Context): Type = + def derivedLambdaAbstraction(paramNames: List[TypeName], paramPrecises: List[Boolean], paramInfos: List[TypeBounds], resType: Type)(using Context): Type = resType match { case resType: AliasingBounds => - resType.derivedAlias(newLikeThis(paramNames, paramInfos, resType.alias)) + resType.derivedAlias(newLikeThis(paramNames, paramPrecises, paramInfos, resType.alias)) case resType @ TypeBounds(lo, hi) => resType.derivedTypeBounds( - if (lo.isRef(defn.NothingClass)) lo else newLikeThis(paramNames, paramInfos, lo), - newLikeThis(paramNames, paramInfos, hi)) + if (lo.isRef(defn.NothingClass)) lo else newLikeThis(paramNames, paramPrecises, paramInfos, lo), + newLikeThis(paramNames, paramPrecises, paramInfos, hi)) case _ => - derivedLambdaType(paramNames, paramInfos, resType) + derivedLambdaType(paramNames, paramPrecises, paramInfos, resType) } } @@ -4037,7 +4047,7 @@ object Types { * * Variances are stored in the `typeParams` list of the lambda. */ - class HKTypeLambda(val paramNames: List[TypeName], @constructorOnly variances: List[Variance])( + class HKTypeLambda(val paramNames: List[TypeName], val declaredParamPrecises: List[Boolean], @constructorOnly variances: List[Variance])( paramInfosExp: HKTypeLambda => List[TypeBounds], resultTypeExp: HKTypeLambda => Type) extends HKLambda with TypeLambda { type This = HKTypeLambda @@ -4059,7 +4069,7 @@ object Types { else Nil override def computeHash(bs: Binders): Int = - doHash(new SomeBinders(this, bs), declaredVariances ::: paramNames, resType, paramInfos) + doHash(new SomeBinders(this, bs), declaredParamPrecises ::: declaredVariances ::: paramNames, resType, paramInfos) // No definition of `eql` --> fall back on equals, which calls iso @@ -4081,16 +4091,16 @@ object Types { false } - override def newLikeThis(paramNames: List[ThisName], paramInfos: List[PInfo], resType: Type)(using Context): This = - newLikeThis(paramNames, declaredVariances, paramInfos, resType) + override def newLikeThis(paramNames: List[ThisName], paramPrecises: List[Boolean], paramInfos: List[PInfo], resType: Type)(using Context): This = + newLikeThis(paramNames, paramPrecises, declaredVariances, paramInfos, resType) - def newLikeThis(paramNames: List[ThisName], variances: List[Variance], paramInfos: List[PInfo], resType: Type)(using Context): This = - HKTypeLambda(paramNames, variances)( + def newLikeThis(paramNames: List[ThisName], paramPrecises: List[Boolean], variances: List[Variance], paramInfos: List[PInfo], resType: Type)(using Context): This = + HKTypeLambda(paramNames, paramPrecises, variances)( x => paramInfos.mapConserve(_.subst(this, x).asInstanceOf[PInfo]), x => resType.subst(this, x)) def withVariances(variances: List[Variance])(using Context): This = - newLikeThis(paramNames, variances, paramInfos, resType) + newLikeThis(paramNames, paramPrecises, variances, paramInfos, resType) protected def prefixString: String = "HKTypeLambda" final override def toString: String = @@ -4105,7 +4115,7 @@ object Types { /** The type of a polymorphic method. It has the same form as HKTypeLambda, * except it applies to terms and parameters do not have variances. */ - class PolyType(val paramNames: List[TypeName])( + class PolyType(val paramNames: List[TypeName], val declaredParamPrecises: List[Boolean])( paramInfosExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type) extends MethodOrPoly with TypeLambda { @@ -4132,7 +4142,7 @@ object Types { case t => mapOver(t) } } - PolyType(paramNames ++ that.paramNames)( + PolyType(paramNames ++ that.paramNames, paramPrecises ++ that.paramPrecises)( x => this.paramInfos.mapConserve(_.subst(this, x).bounds) ++ that.paramInfos.mapConserve(shiftedSubst(x)(_).bounds), x => shiftedSubst(x)(that.resultType).subst(this, x)) @@ -4143,21 +4153,21 @@ object Types { } object HKTypeLambda extends TypeLambdaCompanion[HKTypeLambda] { - def apply(paramNames: List[TypeName])( + def apply(paramNames: List[TypeName], paramPrecises: List[Boolean])( paramInfosExp: HKTypeLambda => List[TypeBounds], resultTypeExp: HKTypeLambda => Type)(using Context): HKTypeLambda = - apply(paramNames, Nil)(paramInfosExp, resultTypeExp) + apply(paramNames, paramPrecises, Nil)(paramInfosExp, resultTypeExp) - def apply(paramNames: List[TypeName], variances: List[Variance])( + def apply(paramNames: List[TypeName], paramPrecises: List[Boolean], variances: List[Variance])( paramInfosExp: HKTypeLambda => List[TypeBounds], resultTypeExp: HKTypeLambda => Type)(using Context): HKTypeLambda = - unique(new HKTypeLambda(paramNames, variances)(paramInfosExp, resultTypeExp)) + unique(new HKTypeLambda(paramNames, paramPrecises, variances)(paramInfosExp, resultTypeExp)) def unapply(tl: HKTypeLambda): Some[(List[LambdaParam], Type)] = Some((tl.typeParams, tl.resType)) def any(n: Int)(using Context): HKTypeLambda = - apply(syntheticParamNames(n))( + apply(syntheticParamNames(n), Nil)( pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType) override def fromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], resultType: Type)(using Context): Type = @@ -4192,7 +4202,7 @@ object Types { def boundsFromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], bounds: TypeBounds)(using Context): TypeBounds = { def expand(tp: Type, useVariances: Boolean) = if params.nonEmpty && useVariances then - apply(params.map(_.paramName), params.map(_.paramVariance))( + apply(params.map(_.paramName), params.map(_.paramPrecise), params.map(_.paramVariance))( tl => params.map(param => toPInfo(tl.integrate(params, param.paramInfo))), tl => tl.integrate(params, tp)) else @@ -4215,10 +4225,10 @@ object Types { } object PolyType extends TypeLambdaCompanion[PolyType] { - def apply(paramNames: List[TypeName])( + def apply(paramNames: List[TypeName], paramPrecises: List[Boolean])( paramInfosExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type)(using Context): PolyType = - unique(new PolyType(paramNames)(paramInfosExp, resultTypeExp)) + unique(new PolyType(paramNames, paramPrecises)(paramInfosExp, resultTypeExp)) def unapply(tl: PolyType): Some[(List[LambdaParam], Type)] = Some((tl.typeParams, tl.resType)) @@ -4247,6 +4257,7 @@ object Types { def paramInfoAsSeenFrom(pre: Type)(using Context): tl.PInfo = paramInfo def paramInfoOrCompleter(using Context): Type = paramInfo def paramRef(using Context): Type = tl.paramRefs(n) + def paramPrecise(using Context): Boolean = if (tl.paramPrecises.nonEmpty) tl.paramPrecises(n) else false private var myVariance: FlagSet = UndefinedFlags @@ -5554,7 +5565,7 @@ object Types { tp.derivedExprType(restpe) // note: currying needed because Scala2 does not support param-dependencies protected def derivedLambdaType(tp: LambdaType)(formals: List[tp.PInfo], restpe: Type): Type = - tp.derivedLambdaType(tp.paramNames, formals, restpe) + tp.derivedLambdaType(tp.paramNames, tp.paramPrecises, formals, restpe) protected def mapArgs(args: List[Type], tparams: List[ParamInfo]): List[Type] = args match case arg :: otherArgs if tparams.nonEmpty => @@ -6011,7 +6022,7 @@ object Types { derivedLambdaType(tp)(formals.map(upper(_).asInstanceOf[tp.PInfo]), restpe), derivedLambdaType(tp)(formals.map(lower(_).asInstanceOf[tp.PInfo]), restpe)) else - tp.derivedLambdaType(tp.paramNames, formals, restpe) + tp.derivedLambdaType(tp.paramNames, tp.paramPrecises, formals, restpe) } override def mapCapturingType(tp: Type, parent: Type, refs: CaptureSet, v: Int): Type = diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 0b5fda49d63c..7229fa523da3 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -291,7 +291,7 @@ class ClassfileParser( normalizedParamNames = paramNames.dropRight(1) normalizedParamTypes = paramTypes.dropRight(1) } - denot.info = mt.derivedLambdaType(normalizedParamNames, normalizedParamTypes, resultType) + denot.info = mt.derivedLambdaType(normalizedParamNames, Nil, normalizedParamTypes, resultType) case _ => } @@ -302,8 +302,8 @@ class ClassfileParser( val rt = classRoot.typeRef appliedTo (classRoot.typeParams map (_.typeRef)) def resultType(tpe: Type): Type = tpe match { - case mt @ MethodType(paramNames) => mt.derivedLambdaType(paramNames, mt.paramInfos, rt) - case pt : PolyType => pt.derivedLambdaType(pt.paramNames, pt.paramInfos, resultType(pt.resType)) + case mt @ MethodType(paramNames) => mt.derivedLambdaType(paramNames, Nil, mt.paramInfos, rt) + case pt : PolyType => pt.derivedLambdaType(pt.paramNames, pt.paramPrecises, pt.paramInfos, resultType(pt.resType)) } denot.info = resultType(denot.info) @@ -714,7 +714,7 @@ class ClassfileParser( case mt @ MethodType(oldp) if namedParams.nonEmpty => mt.derivedLambdaType(List.tabulate(oldp.size)(n => namedParams.getOrElse(n, oldp(n)))) case pt: PolyType if namedParams.nonEmpty => - pt.derivedLambdaType(pt.paramNames, pt.paramInfos, fillInParamNames(pt.resultType)) + pt.derivedLambdaType(pt.paramNames, pt.paramPrecises, pt.paramInfos, fillInParamNames(pt.resultType)) case _ => t cook.apply(fillInParamNames(newType)) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 6887937ed6fe..45615a3b0ca7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -329,7 +329,7 @@ class TreeUnpickler(reader: TastyReader, nameReader.skipTree() // skip result val paramReader = nameReader.fork val (paramNames, mods) = nameReader.readParamNamesAndMods(end) - companionOp(mods)(paramNames.map(nameMap))( + companionOp(mods)(paramNames.map(nameMap), Nil)( pt => registeringType(pt, paramReader.readParamTypes[PInfo](paramNames.length)), pt => readType()) }) diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index 7e47bbfdfa8a..1963170635c4 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -167,7 +167,7 @@ object PrepareInlineable { // Add qualifier type as leading method argument to argument `tp` def addQualType(tp: Type): Type = tp match { - case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, addQualType(tp.resultType)) + case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramPrecises, tp.paramInfos, addQualType(tp.resultType)) case tp: ExprType => addQualType(tp.resultType) case tp => MethodType(qualType.simplified :: Nil, tp) } diff --git a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala index be454281bcbb..6e2f76991d1c 100644 --- a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala +++ b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala @@ -59,6 +59,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => case _ => tp.derivedLambdaType( paramNames = tp.paramNames ++ List(nameParamName, ordinalParamName), + paramPrecises = Nil, paramInfos = tp.paramInfos ++ List(defn.StringType, defn.IntType)) } } diff --git a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index 503561915040..3e037505bb7a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -21,7 +21,7 @@ object ElimErasedValueType { case tp: MethodType => val paramTypes = tp.paramInfos.mapConserve(elimEVT) val retType = elimEVT(tp.resultType) - tp.derivedLambdaType(tp.paramNames, paramTypes, retType) + tp.derivedLambdaType(tp.paramNames, Nil, paramTypes, retType) case _ => tp } diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index bdc2a268c1f8..c0c94fb136e8 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -117,9 +117,9 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => paramTypes.updated(lastIdx, last.translateFromRepeated(toArray = isJava)) else paramTypes else paramTypes - tp.derivedLambdaType(paramNames, paramTypes1, resultType1) + tp.derivedLambdaType(paramNames, Nil, paramTypes1, resultType1) case tp: PolyType => - tp.derivedLambdaType(tp.paramNames, tp.paramInfos, elimRepeated(tp.resultType, isJava)) + tp.derivedLambdaType(tp.paramNames, tp.paramPrecises, tp.paramInfos, elimRepeated(tp.resultType, isJava)) case tp => tp @@ -270,15 +270,15 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => /** Convert type from Scala to Java varargs method */ private def toJavaVarArgs(tp: Type)(using Context): Type = tp match case tp: PolyType => - tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType)) + tp.derivedLambdaType(tp.paramNames, tp.paramPrecises, tp.paramInfos, toJavaVarArgs(tp.resultType)) case tp: MethodType => tp.resultType match case m: MethodType => // multiple param lists - tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(m)) + tp.derivedLambdaType(tp.paramNames, Nil, tp.paramInfos, toJavaVarArgs(m)) case _ => val init :+ last = tp.paramInfos: @unchecked val vararg = varargArrayType(last) - tp.derivedLambdaType(tp.paramNames, init :+ vararg, tp.resultType) + tp.derivedLambdaType(tp.paramNames, Nil, init :+ vararg, tp.resultType) /** Translate a repeated type T* to an `Array[? <: Upper]` * such that it is compatible with java varargs. diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala index ed3bfc7c0181..7c8a009c4ce2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -378,7 +378,7 @@ object ExplicitOuter { if (needsOuterParam(cls)) { val mt @ MethodTpe(pnames, ptypes, restpe) = tp: @unchecked mt.derivedLambdaType( - nme.OUTER :: pnames, outerClass(cls).typeRef :: ptypes, restpe) + nme.OUTER :: pnames, Nil, outerClass(cls).typeRef :: ptypes, restpe) } else tp diff --git a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala index 8ca600577244..5e2524da9d3e 100644 --- a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -96,6 +96,7 @@ trait FullParameterization { } val ctparams = if (abstractOverClass) clazz.typeParams else Nil val ctnames = ctparams.map(_.name) + val ctprecises = ctparams.map(_.paramPrecise) /** The method result type */ def resultType(mapClassParams: Type => Type) = { @@ -118,14 +119,14 @@ trait FullParameterization { info match { case info: PolyType => - PolyType(info.paramNames ++ ctnames)( + PolyType(info.paramNames ++ ctnames, info.paramPrecises ++ ctprecises)( pt => (info.paramInfos.map(mapClassParams(_, pt).bounds) ++ mappedClassBounds(pt)).mapConserve(_.subst(info, pt).bounds), pt => resultType(mapClassParams(_, pt)).subst(info, pt)) case _ => if (ctparams.isEmpty) resultType(identity) - else PolyType(ctnames)(mappedClassBounds, pt => resultType(mapClassParams(_, pt))) + else PolyType(ctnames, ctprecises)(mappedClassBounds, pt => resultType(mapClassParams(_, pt))) } } @@ -263,7 +264,7 @@ object FullParameterization { case MethodTpe(nme.SELF :: Nil, _, restpe) => restpe.ensureMethodic.signature case info @ MethodTpe(nme.SELF :: otherNames, thisType :: otherTypes, restpe) => - info.derivedLambdaType(otherNames, otherTypes, restpe).signature + info.derivedLambdaType(otherNames, Nil, otherTypes, restpe).signature case _ => Signature.NotAMethod } diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala index b0de197635e9..e75fe6062607 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala @@ -442,7 +442,7 @@ object PrepJSExports { case _: ExprType => ExprType(defn.AnyType) case tpe: PolyType => - PolyType(tpe.paramNames)( + PolyType(tpe.paramNames, tpe.paramPrecises)( x => tpe.paramInfos.mapConserve(_.subst(tpe, x).bounds), x => finalResultTypeToAny(tpe.resultType.subst(tpe, x))) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index e11afe261458..a8cf22cf773a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1478,7 +1478,7 @@ trait Applications extends Compatibility { case mt: MethodType if mt.isImplicitMethod => stripImplicit(resultTypeApprox(mt)) case pt: PolyType => - pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType)).asInstanceOf[PolyType].flatten + pt.derivedLambdaType(pt.paramNames, pt.paramPrecises, pt.paramInfos, stripImplicit(pt.resultType)).asInstanceOf[PolyType].flatten case _ => tp } @@ -1588,7 +1588,7 @@ trait Applications extends Compatibility { // contain uninstantiated TypeVars, this could lead to cycles in // `isSubType` as a TypeVar might get constrained by a TypeRef it's // part of. - val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType) + val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramPrecises, tp1.paramInfos, defn.AnyType) fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.srcPos) val tparams = newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_)) @@ -1677,9 +1677,9 @@ trait Applications extends Compatibility { */ def widenGiven(tp: Type, alt: TermRef): Type = tp match { case mt: MethodType if mt.isImplicitMethod => - mt.derivedLambdaType(mt.paramNames, mt.paramInfos, widenGiven(mt.resultType, alt)) + mt.derivedLambdaType(mt.paramNames, Nil, mt.paramInfos, widenGiven(mt.resultType, alt)) case pt: PolyType => - pt.derivedLambdaType(pt.paramNames, pt.paramInfos, widenGiven(pt.resultType, alt)) + pt.derivedLambdaType(pt.paramNames, pt.paramPrecises, pt.paramInfos, widenGiven(pt.resultType, alt)) case rt => if alt.symbol.isCoDefinedGiven(rt.typeSymbol) then tp.widenToParents else tp @@ -2298,11 +2298,11 @@ trait Applications extends Compatibility { // The return type after truncation is not important def truncateExtension(tp: Type)(using Context): Type = tp match case poly: PolyType => - poly.newLikeThis(poly.paramNames, poly.paramInfos, truncateExtension(poly.resType)) + poly.newLikeThis(poly.paramNames, poly.paramPrecises, poly.paramInfos, truncateExtension(poly.resType)) case meth: MethodType if meth.isContextualMethod => - meth.newLikeThis(meth.paramNames, meth.paramInfos, truncateExtension(meth.resType)) + meth.newLikeThis(meth.paramNames, meth.paramPrecises, meth.paramInfos, truncateExtension(meth.resType)) case meth: MethodType => - meth.newLikeThis(meth.paramNames, meth.paramInfos, defn.AnyType) + meth.newLikeThis(meth.paramNames, meth.paramPrecises, meth.paramInfos, defn.AnyType) def replaceCallee(inTree: Tree, replacement: Tree)(using Context): Tree = inTree match case Apply(fun, args) => Apply(replaceCallee(fun, replacement), args) diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index d2165a5ca8c5..63918eb81138 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -169,7 +169,7 @@ trait Deriving { else { val derivedParamTypes = derivedParams.map(_.typeRef) - HKTypeLambda(typeClassParamInfos.map(_.paramName))( + HKTypeLambda(typeClassParamInfos.map(_.paramName), typeClassParamInfos.map(_.paramPrecise))( tl => typeClassParamInfos.map(_.paramInfo.bounds), tl => clsType.appliedTo(derivedParamTypes ++ tl.paramRefs.takeRight(clsArity))) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ad8d0e50d348..7dfbd9c9c59b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1193,6 +1193,7 @@ class Namer { typer: Typer => paramNames = info.paramNames.mapConserve { pname => if defines(pathType, pname) then pname.freshened else pname }, + paramPrecises = info.paramPrecises, resType = avoidNameClashes(info.resType)) case info => info diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 71b500dc04a9..bd76a3b9c741 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -733,7 +733,7 @@ object ProtoTypes { def newTypeVar(using Context)( bounds: TypeBounds, name: TypeName = DepParamName.fresh().toTypeName, nestingLevel: Int = ctx.nestingLevel, represents: Type = NoType): TypeVar = - val poly = PolyType(name :: Nil)( + val poly = PolyType(name :: Nil, Nil)( pt => bounds :: Nil, pt => represents.orElse(defn.AnyType)) constrained(poly, untpd.EmptyTree, alwaysAddTypeVars = true, nestingLevel) @@ -804,7 +804,7 @@ object ProtoTypes { tp case pt: ApplyingProto => if (rt eq mt.resultType) tp - else mt.derivedLambdaType(mt.paramNames, mt.paramInfos, rt) + else mt.derivedLambdaType(mt.paramNames, Nil, mt.paramInfos, rt) case _ => val ft = defn.FunctionOf(mt.paramInfos, rt) if mt.paramInfos.nonEmpty || (ft frozen_<:< pt) then ft else rt diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index c8b01b3407b7..2ebdcdb7fe35 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -314,6 +314,7 @@ trait TypeAssigner { case pt: TypeLambda => tree.withType { val paramNames = pt.paramNames + val paramPrecises = pt.paramPrecises if (hasNamedArg(args)) { val paramBoundsByName = paramNames.zip(pt.paramInfos).toMap @@ -353,6 +354,7 @@ trait TypeAssigner { val gaps = gapBuf.toList pt.derivedLambdaType( gaps.map(paramNames), + gaps.map(paramPrecises), gaps.map(idx => transform(pt.paramInfos(idx)).bounds), resultType1) } @@ -366,7 +368,7 @@ trait TypeAssigner { // See pos/i6682a.scala for a test case where the defensive copying matters. val ensureFresh = new TypeMap with CaptureSet.IdempotentCaptRefMap: def apply(tp: Type) = mapOver( - if tp eq pt then pt.newLikeThis(pt.paramNames, pt.paramInfos, pt.resType) + if tp eq pt then pt.newLikeThis(pt.paramNames, pt.paramPrecises, pt.paramInfos, pt.resType) else tp) val argTypes = args.tpes.mapConserve(ensureFresh) if (argTypes.hasSameLengthAs(paramNames)) pt.instantiate(argTypes) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index f8e439baeb0e..f3af35af1dd9 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -20,6 +20,7 @@ import dotty.tools.dotc.quoted.{MacroExpansion, PickledQuotes} import scala.quoted.runtime.{QuoteUnpickler, QuoteMatching} import scala.quoted.runtime.impl.printers._ +import scala.annotation.experimental import scala.reflect.TypeTest @@ -2147,7 +2148,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object PolyType extends PolyTypeModule: def apply(paramNames: List[String])(paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => TypeRepr): PolyType = - Types.PolyType(paramNames.map(_.toTypeName))(paramBoundsExp, resultTypeExp) + Types.PolyType(paramNames.map(_.toTypeName), Nil)(paramBoundsExp, resultTypeExp) + @experimental //TODO: when ending the experimental period of @precise, the apply methods should be combined with `paramPrecises = Nil` default + def apply(paramNames: List[String], paramPrecises: List[Boolean])(paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => TypeRepr): PolyType = + Types.PolyType(paramNames.map(_.toTypeName), paramPrecises)(paramBoundsExp, resultTypeExp) def unapply(x: PolyType): (List[String], List[TypeBounds], TypeRepr) = (x.paramNames.map(_.toString), x.paramBounds, x.resType) end PolyType @@ -2169,7 +2173,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object TypeLambda extends TypeLambdaModule: def apply(paramNames: List[String], boundsFn: TypeLambda => List[TypeBounds], bodyFn: TypeLambda => TypeRepr): TypeLambda = - Types.HKTypeLambda(paramNames.map(_.toTypeName))(boundsFn, bodyFn) + Types.HKTypeLambda(paramNames.map(_.toTypeName), Nil)(boundsFn, bodyFn) + @experimental //TODO: when ending the experimental period of @precise, the apply methods should be combined with `paramPrecises = Nil` default + def apply(paramNames: List[String], boundsFn: TypeLambda => List[TypeBounds], bodyFn: TypeLambda => TypeRepr, paramPrecises: List[Boolean]): TypeLambda = + Types.HKTypeLambda(paramNames.map(_.toTypeName), paramPrecises)(boundsFn, bodyFn) def unapply(x: TypeLambda): (List[String], List[TypeBounds], TypeRepr) = (x.paramNames.map(_.toString), x.paramBounds, x.resType) end TypeLambda diff --git a/compiler/test/dotty/tools/dotc/StringFormatterTest.scala b/compiler/test/dotty/tools/dotc/StringFormatterTest.scala index 7df64ad5bf3f..e6bb1cea69f6 100644 --- a/compiler/test/dotty/tools/dotc/StringFormatterTest.scala +++ b/compiler/test/dotty/tools/dotc/StringFormatterTest.scala @@ -88,7 +88,7 @@ abstract class AbstractStringFormatterTest extends DottyTest: def mkCstrd = val names = List(typeName("Foo"), typeName("Bar")) val infos = List(TypeBounds.upper(defn.IntType), TypeBounds.upper(defn.StringType)) - val tl = PolyType(names)(_ => infos, _ => defn.AnyType) + val tl = PolyType(names, Nil)(_ => infos, _ => defn.AnyType) TypeComparer.addToConstraint(tl, Nil) tl.paramRefs diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 3e2863f2260b..df7ff85f5e7a 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3138,6 +3138,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val PolyType` */ trait PolyTypeModule { this: PolyType.type => def apply(paramNames: List[String])(paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => TypeRepr): PolyType + @experimental //TODO: when ending the experimental period of @precise, the apply methods should be combined with `paramPrecises = Nil` default + def apply(paramNames: List[String], paramPrecises: List[Boolean])(paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => TypeRepr): PolyType def unapply(x: PolyType): (List[String], List[TypeBounds], TypeRepr) } @@ -3164,6 +3166,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val TypeLambda` */ trait TypeLambdaModule { this: TypeLambda.type => def apply(paramNames: List[String], boundsFn: TypeLambda => List[TypeBounds], bodyFn: TypeLambda => TypeRepr): TypeLambda + @experimental //TODO: when ending the experimental period of @precise, the apply methods should be combined with `paramPrecises = Nil` default + def apply(paramNames: List[String], boundsFn: TypeLambda => List[TypeBounds], bodyFn: TypeLambda => TypeRepr, paramPrecises: List[Boolean]): TypeLambda def unapply(x: TypeLambda): (List[String], List[TypeBounds], TypeRepr) } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index ac68190d441d..9273a830be8c 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,5 +3,11 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( + // APIs will be added in 3.3.0 + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.precise"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#PolyTypeModule.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#PolyTypeModule.apply"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeLambdaModule.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeLambdaModule.apply"), ) } diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index e8e2f0c08179..8346ff90c2ab 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -65,6 +65,11 @@ val experimentalDefinitionInLibrary = Set( // Need experimental annotation macros to check that design works. "scala.quoted.Quotes.reflectModule.ClassDefModule.apply", "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", + + ////New APIs: precise + "scala.annotation.precise", + "scala.quoted.Quotes.reflectModule.PolyTypeModule.apply", + "scala.quoted.Quotes.reflectModule.TypeLambdaModule.apply" ) From b02e33ddb9cd5b44616cb402d08fead86ec4573c Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 16:57:13 -0400 Subject: [PATCH 05/17] add `isPrecise` indicator to `Type` and modify printer to reflect that --- .../src/dotty/tools/dotc/core/Types.scala | 48 ++++++++++++++++++- .../tools/dotc/printing/PlainPrinter.scala | 9 ++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 043733d64802..05e606af9221 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -438,6 +438,9 @@ object Types { /** Is this a higher-kinded type lambda with given parameter variances? */ def isDeclaredVarianceLambda: Boolean = false + /** Is this a precise-enforced type? */ + def isPrecise(using Context): Boolean = false + /** Does this type contain wildcard types? */ final def containsWildcardTypes(using Context) = existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false) @@ -3514,7 +3517,7 @@ object Types { extends CachedProxyType with MethodicType { override def resultType(using Context): Type = resType override def underlying(using Context): Type = resType - + override def isPrecise(using Context): Boolean = resType.isPrecise override def signature(using Context): Signature = Signature.NotAMethod def derivedExprType(resType: Type)(using Context): ExprType = @@ -4342,6 +4345,8 @@ object Types { override def underlying(using Context): Type = tycon + override def isPrecise(using Context): Boolean = defn.isFunctionType(this) && args.last.isPrecise + override def superType(using Context): Type = if ctx.period != validSuper then validSuper = if (tycon.isProvisional) Nowhere else ctx.period @@ -4526,6 +4531,37 @@ object Types { type BT = TypeLambda def kindString: String = "Type" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) + private var preciseSubstitute: Boolean = false + private var cachedPrecise: Option[Boolean] = None + final protected[Types] def setPreciseSubstitute(p: Boolean): Unit = preciseSubstitute = p + private def isPreciseRecur(tp: Type, boringPrecise: Boolean)(using Context): Option[Boolean] = tp match + case p: TypeParamRef if p == this => Some(boringPrecise) + case at@AppliedType(_, args) => + args + .lazyZip(at.tyconTypeParams.view.map(p => p.paramPrecise || boringPrecise)).view + .map(isPreciseRecur).collectFirst { + case Some(res) => res + } + case _ => None + + override def isPrecise(using Context): Boolean = + // the param is substituting a precise type or... + preciseSubstitute || cachedPrecise.getOrElse { + val precise = + // the param itself is annotated as precise or the param is first introduced + // in a precise position of an applied type argument + binder.paramPrecises(paramNum) || + (binder.resType.dealiasKeepOpaques match + //givens just return an applied type which we use + case at: AppliedType => isPreciseRecur(at, false) + //for method types we go over the arguments + case rt => rt.paramInfoss.view.flatten.flatMap(isPreciseRecur(_, false)).headOption + ).getOrElse(false) + cachedPrecise = Some(precise) + precise + } || + //the param upper-bounded by a precise type + ctx.typerState.constraint.minUpper(this).filter(_.paramName != this.paramName).exists(_.isPrecise) /** Optimized version of occursIn, avoid quadratic blowup when solving * constraints over large ground types. @@ -4649,12 +4685,18 @@ object Types { */ def setOrigin(p: TypeParamRef) = currentOrigin = p + override def isPrecise(using Context): Boolean = currentOrigin.isPrecise /** The permanent instance type of the variable, or NoType is none is given yet */ private var myInst: Type = NoType private[core] def inst: Type = myInst - private[core] def setInst(tp: Type): Unit = + private[core] def setInst(tp: Type)(using Context): Unit = myInst = tp + //propagating precise indicator to the instance type parameter + tp match + case v : TypeVar if tp.exists => + v.origin.setPreciseSubstitute(origin.isPrecise) + case _ => if tp.exists && owningState != null then val owningState1 = owningState.uncheckedNN.get if owningState1 != null then @@ -5218,6 +5260,8 @@ object Types { override def stripped(using Context): Type = parent.stripped + override def isPrecise(using Context): Boolean = parent.isPrecise + private var isRefiningKnown = false private var isRefiningCache: Boolean = _ diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index d62b7afef707..46841ef6cd52 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -287,9 +287,12 @@ class PlainPrinter(_ctx: Context) extends Printer { "(" ~ toTextRef(tp) ~ " : " ~ toTextGlobal(tp.underlying) ~ ")" protected def paramsText(lam: LambdaType): Text = { - def paramText(name: Name, tp: Type) = - toText(name) ~ lambdaHash(lam) ~ toTextRHS(tp, isParameter = true) - Text(lam.paramNames.lazyZip(lam.paramInfos).map(paramText), ", ") + def paramText(name: Name, tp: Type, precise: Boolean) = + (if precise then "@precise " else "") ~ toText(name) ~ lambdaHash(lam) ~ toTextRHS(tp, isParameter = true) + val precises = lam match + case pt: TypeLambda => pt.paramPrecises + case _ => lam.paramNames.map(_ => false) + Text(lam.paramNames.lazyZip(lam.paramInfos).lazyZip(precises).toList.map(paramText), ", ") } protected def ParamRefNameString(name: Name): String = nameString(name) From aafcf55d002d38ab7f9e25c53a9a2433601f75b7 Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:06:51 -0400 Subject: [PATCH 06/17] add `precise` indicator to `WildcardType` and propagate it from/to `TypeParamRef` --- .../tools/dotc/core/ConstraintHandling.scala | 2 +- .../tools/dotc/core/TypeApplications.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 28 +++++++++++-------- .../dotty/tools/dotc/transform/Recheck.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 14 +++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +-- tests/pos-with-compiler/Patterns.scala | 2 +- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 1dfa04822766..396300cb7f32 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -643,7 +643,7 @@ trait ConstraintHandling { if (tpw ne tp) && (tpw <:< bound) then tpw else tp def isSingleton(tp: Type): Boolean = tp match - case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi) + case WildcardType(optBounds, _) => optBounds.exists && isSingleton(optBounds.bounds.hi) case _ => isSubTypeWhenFrozen(tp, defn.SingletonType) val wideInst = diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 58f9732edf1f..ed089cd15ee4 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -383,7 +383,7 @@ class TypeApplications(val self: Type) extends AnyVal { case dealiased: LazyRef => LazyRef.of(dealiased.ref.appliedTo(args)) case dealiased: WildcardType => - WildcardType(dealiased.optBounds.orElse(TypeBounds.empty).appliedTo(args).bounds) + WildcardType(dealiased.optBounds.orElse(TypeBounds.empty).appliedTo(args).bounds, dealiased.precise) case dealiased: TypeRef if dealiased.symbol == defn.NothingClass => dealiased case dealiased => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 05e606af9221..ee0cda5a287f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -365,7 +365,7 @@ object Types { case TypeBounds(lo, hi) => lo.unusableForInference || hi.unusableForInference case tp: AndOrType => tp.tp1.unusableForInference || tp.tp2.unusableForInference case tp: LambdaType => tp.resultType.unusableForInference || tp.paramInfos.exists(_.unusableForInference) - case WildcardType(optBounds) => optBounds.unusableForInference + case WildcardType(optBounds, _) => optBounds.unusableForInference case CapturingType(parent, refs) => parent.unusableForInference || refs.elems.exists(_.unusableForInference) case _: ErrorType => true case _ => false @@ -5361,7 +5361,7 @@ object Types { object TryDynamicCallType extends FlexType /** Wildcard type, possibly with bounds */ - abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType { + abstract case class WildcardType(optBounds: Type, precise: Boolean) extends CachedGroundType with TermType { def effectiveBounds(using Context): TypeBounds = optBounds match case bounds: TypeBounds => bounds @@ -5370,11 +5370,11 @@ object Types { def derivedWildcardType(optBounds: Type)(using Context): WildcardType = if (optBounds eq this.optBounds) this else if (!optBounds.exists) WildcardType - else WildcardType(optBounds.asInstanceOf[TypeBounds]) + else WildcardType(optBounds.asInstanceOf[TypeBounds], precise) - override def computeHash(bs: Binders): Int = doHash(bs, optBounds) + override def computeHash(bs: Binders): Int = doHash(bs, precise, optBounds) override def hashIsStable: Boolean = optBounds.hashIsStable - + override def isPrecise(using Context): Boolean = precise override def eql(that: Type): Boolean = that match { case that: WildcardType => optBounds.eq(that.optBounds) case _ => false @@ -5388,18 +5388,18 @@ object Types { } } - final class CachedWildcardType(optBounds: Type) extends WildcardType(optBounds) + final class CachedWildcardType(optBounds: Type, precise: Boolean) extends WildcardType(optBounds, precise) - @sharable object WildcardType extends WildcardType(NoType) { - def apply(bounds: TypeBounds)(using Context): WildcardType = + @sharable object WildcardType extends WildcardType(NoType, false) { + def apply(bounds: TypeBounds, precise: Boolean)(using Context): WildcardType = if bounds eq TypeBounds.empty then val result = ctx.base.emptyWildcardBounds if result == null then - ctx.base.emptyWildcardBounds = unique(CachedWildcardType(bounds)) - apply(bounds) + ctx.base.emptyWildcardBounds = unique(CachedWildcardType(bounds, precise)) + apply(bounds, precise) else result - else unique(CachedWildcardType(bounds)) + else unique(CachedWildcardType(bounds, precise)) } /** An extractor for single abstract method types. @@ -6095,7 +6095,11 @@ object Types { val bounds = t.effectiveBounds range(atVariance(-variance)(apply(bounds.lo)), apply(bounds.hi)) def apply(t: Type): Type = t match - case t: WildcardType => mapWild(t) + case t: WildcardType => mapWild(t) match + case tv: TypeVar => + tv.origin.setPreciseSubstitute(t.isPrecise) + tv + case t => t case _ => mapOver(t) // ----- TypeAccumulators ---------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 36044e6bcb91..c9cbe1980227 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -301,7 +301,7 @@ abstract class Recheck extends Phase, SymTransformer: def recheckSeqLiteral(tree: SeqLiteral, pt: Type)(using Context): Type = val elemProto = pt.stripNull.elemType match case NoType => WildcardType - case bounds: TypeBounds => WildcardType(bounds) + case bounds: TypeBounds => WildcardType(bounds, pt.isPrecise) case elemtp => elemtp val declaredElemType = recheck(tree.elemtpt) val elemTypes = tree.elems.map(recheck(_, elemProto)) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index bd76a3b9c741..6f932080775d 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -767,7 +767,7 @@ object ProtoTypes { || ctx.mode.is(Mode.TypevarsMissContext) || !ref.underlying.widenExpr.isValueTypeOrWildcard then - WildcardType(ref.underlying.substParams(mt, mt.paramRefs.map(_ => WildcardType)).toBounds) + WildcardType(ref.underlying.substParams(mt, mt.paramRefs.map(_ => WildcardType)).toBounds, ref.isPrecise) else newDepTypeVar(ref) mt.resultType.substParams(mt, mt.paramRefs.map(replacement)) @@ -825,7 +825,7 @@ object ProtoTypes { private def wildApprox(tp: Type, theMap: WildApproxMap | Null, seen: Set[TypeParamRef], internal: Set[TypeLambda])(using Context): Type = tp match { case tp: NamedType => // default case, inlined for speed val isPatternBoundTypeRef = tp.isInstanceOf[TypeRef] && tp.symbol.isPatternBound - if (isPatternBoundTypeRef) WildcardType(tp.underlying.bounds) + if (isPatternBoundTypeRef) WildcardType(tp.underlying.bounds, tp.isPrecise) else if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(wildApprox(tp.prefix, theMap, seen, internal)) case tp @ AppliedType(tycon, args) => @@ -849,7 +849,7 @@ object ProtoTypes { case tp @ TypeParamRef(poly, pnum) => def wildApproxBounds(bounds: TypeBounds) = if (seen.contains(tp)) WildcardType - else WildcardType(wildApprox(bounds, theMap, seen + tp, internal).bounds) + else WildcardType(wildApprox(bounds, theMap, seen + tp, internal).bounds, tp.isPrecise) def unconstrainedApprox = wildApproxBounds(poly.paramInfos(pnum)) def approxPoly = if (ctx.mode.is(Mode.TypevarsMissContext)) unconstrainedApprox @@ -860,8 +860,8 @@ object ProtoTypes { case inst => wildApprox(inst, theMap, seen, internal) } approxPoly - case TermParamRef(mt, pnum) => - WildcardType(TypeBounds.upper(wildApprox(mt.paramInfos(pnum), theMap, seen, internal))) + case tp@TermParamRef(mt, pnum) => + WildcardType(TypeBounds.upper(wildApprox(mt.paramInfos(pnum), theMap, seen, internal)), tp.isPrecise) case tp: TypeVar => wildApprox(tp.underlying, theMap, seen, internal) case tp: AndType => @@ -871,7 +871,7 @@ object ProtoTypes { def wildBounds(tp: Type) = if (tp.isInstanceOf[WildcardType]) tp.bounds else TypeBounds.upper(tp) if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) - WildcardType(wildBounds(tp1a) & wildBounds(tp2a)) + WildcardType(wildBounds(tp1a) & wildBounds(tp2a), false) else tp.derivedAndType(tp1a, tp2a) } @@ -881,7 +881,7 @@ object ProtoTypes { val tp1a = wildApprox(tp.tp1, theMap, seen, internal) val tp2a = wildApprox(tp.tp2, theMap, seen, internal) if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) - WildcardType(tp1a.bounds | tp2a.bounds) + WildcardType(tp1a.bounds | tp2a.bounds, false) else tp.derivedOrType(tp1a, tp2a) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b05ba9d1ca43..f043fe529a32 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1197,7 +1197,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def interpolateWildcards = new TypeMap { def apply(t: Type): Type = t match - case WildcardType(bounds: TypeBounds) => + case WildcardType(bounds: TypeBounds, _) => newTypeVar(apply(bounds.orElse(TypeBounds.empty)).bounds) case _ => mapOver(t) } @@ -1886,7 +1886,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral = { val elemProto = pt.stripNull.elemType match { case NoType => WildcardType - case bounds: TypeBounds => WildcardType(bounds) + case bounds: TypeBounds => WildcardType(bounds, pt.isPrecise) case elemtp => elemtp } diff --git a/tests/pos-with-compiler/Patterns.scala b/tests/pos-with-compiler/Patterns.scala index 6492ce6f8c72..b4ba4843f736 100644 --- a/tests/pos-with-compiler/Patterns.scala +++ b/tests/pos-with-compiler/Patterns.scala @@ -12,7 +12,7 @@ object Patterns { } } d match { - case WildcardType(bounds: TypeBounds) => bounds.lo + case WildcardType(bounds: TypeBounds, _) => bounds.lo case a @ Assign(Ident(id), rhs) => id case a: Object => a } From 4ccc7d8895e953b2b0d449db2cbe9818b4a82914 Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:09:33 -0400 Subject: [PATCH 07/17] Fix AST desugaring and manipulation to propagate `@precise` annotations on type parameters. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 3 ++- compiler/src/dotty/tools/dotc/ast/tpd.scala | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 1e1db19bcf25..3825a6b4280b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -474,7 +474,8 @@ object desugar { // Annotations on class _type_ parameters are set on the derived parameters // but not on the constructor parameters. The reverse is true for // annotations on class _value_ parameters. - val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = false)) + val keepAnnotations = cdef.mods.flags.is(Flags.Implicit) + val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = keepAnnotations)) val constrVparamss = if (originalVparamss.isEmpty) { // ensure parameter list is non-empty if (isCaseClass) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 52325e36037d..00e24187b0d2 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -286,6 +286,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { end recur val (rtp, paramss) = recur(sym.info, sym.rawParamss) + val typeParamsSyms = paramss.view.flatten.filter(_.isType).toList + @tailrec def allPrecises(tp: Type, precises: List[Boolean]): List[Boolean] = + tp match + case pt : PolyType => allPrecises(pt.resType, precises ++ pt.paramPrecises) + case mt : MethodType => allPrecises(mt.resType, precises) + case _ => precises + val paramPrecises = allPrecises(sym.info, Nil) + paramPrecises.lazyZip(typeParamsSyms).foreach { + case (true, p) => p.addAnnotation(defn.PreciseAnnot) + case _ => + } DefDef(sym, paramss, rtp, rhsFn(paramss.nestedMap(ref))) end DefDef From da5b035920c59e617f1561f1063b060398912282 Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:10:53 -0400 Subject: [PATCH 08/17] do not widen if the parameter is precise or the mode is `Precise` --- compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 396300cb7f32..4bd401a5af4d 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -647,7 +647,8 @@ trait ConstraintHandling { case _ => isSubTypeWhenFrozen(tp, defn.SingletonType) val wideInst = - if isSingleton(bound) then inst + //keeping the precise type if the bound is Singleton or precise or the mode is precise + if isSingleton(bound) || ctx.mode.is(Mode.Precise) || bound.isPrecise then inst else dropTransparentTraits(widenIrreducible(widenOr(widenSingle(inst))), bound) wideInst match case wideInst: TypeRef if wideInst.symbol.is(Module) => From 20b1831e34bba0322493da402f7f66d6fedec10d Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:21:12 -0400 Subject: [PATCH 09/17] add precise typing for regular and overloaded definitions. precise indicator context is added to the proto. precise mode is activated when precise typing is required, and disabled when there in no-longer an expression context (like Block stats). --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 44 +++++++++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 52 +++++++++++++++++-- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 6f932080775d..aba03408b82e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -328,8 +328,10 @@ object ProtoTypes { case class FunProto(args: List[untpd.Tree], resType: Type)( typer: Typer, override val applyKind: ApplyKind, - state: FunProtoState = new FunProtoState, - val constrainResultDeep: Boolean = false)(using protoCtx: Context) + private var state: FunProtoState = new FunProtoState, + val constrainResultDeep: Boolean = false, + val argsPrecises: List[Boolean] = Nil + )(using protoCtx: Context) extends UncachedGroundType with ApplyingProto with FunOrPolyProto { override def resultType(using Context): Type = resType @@ -341,17 +343,23 @@ object ProtoTypes { typer.isApplicableType(tp, args, resultType, keepConstraint && !args.exists(isPoly)) } + def snapshot: FunProtoState = state + def resetTo(prevState: FunProtoState): Unit = state = prevState + def derivedFunProto( args: List[untpd.Tree] = this.args, resultType: Type = this.resultType, typer: Typer = this.typer, - constrainResultDeep: Boolean = this.constrainResultDeep): FunProto = + constrainResultDeep: Boolean = this.constrainResultDeep, + argsPrecises: List[Boolean] = this.argsPrecises + ): FunProto = if (args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer) && constrainResultDeep == this.constrainResultDeep + && argsPrecises == this.argsPrecises then this - else new FunProto(args, resultType)(typer, applyKind, constrainResultDeep = constrainResultDeep) + else new FunProto(args, resultType)(typer, applyKind, constrainResultDeep = constrainResultDeep, argsPrecises) /** @return True if all arguments have types. */ @@ -434,7 +442,9 @@ object ProtoTypes { val protoTyperState = ctx.typerState val oldConstraint = protoTyperState.constraint val args1 = args.mapWithIndexConserve((arg, idx) => - cacheTypedArg(arg, arg => typer.typed(norm(arg, idx)), force = false)) + val precise = if (argsPrecises.nonEmpty && argsPrecises(idx)) Mode.Precise else Mode.None + withMode(precise){cacheTypedArg(arg, arg => typer.typed(norm(arg, idx)), force = false)} + ) val newConstraint = protoTyperState.constraint if !args1.exists(arg => isUndefined(arg.tpe)) then state.typedArgs = args1 @@ -474,15 +484,21 @@ object ProtoTypes { * used to avoid repeated typings of trees when backtracking. */ def typedArg(arg: untpd.Tree, formal: Type)(using Context): Tree = { - val wideFormal = formal.widenExpr - val argCtx = - if wideFormal eq formal then ctx - else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables) - val locked = ctx.typerState.ownedVars - val targ = cacheTypedArg(arg, - typer.typedUnadapted(_, wideFormal, locked)(using argCtx), - force = true) - typer.adapt(targ, wideFormal, locked) + val precise = formal match + case v if v.isPrecise => Mode.Precise + case _ if argsPrecises.nonEmpty && argsPrecises(args.indexOf(arg)) => Mode.Precise + case _ => Mode.None + withMode(precise) { + val wideFormal = formal.widenExpr + val argCtx = + if wideFormal eq formal then ctx + else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables) + val locked = ctx.typerState.ownedVars + val targ = cacheTypedArg(arg, + typer.typedUnadapted(_, wideFormal, locked)(using argCtx), + force = true) + typer.adapt(targ, wideFormal, locked) + } } /** The type of the argument `arg`, or `NoType` if `arg` has not been typed before diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f043fe529a32..533212f02e2d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1061,7 +1061,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = { val (stats1, exprCtx) = withoutMode(Mode.Pattern) { - typedBlockStats(tree.stats) + // in all cases except a closure block, we disable precise type enforcement + tree.expr match + case _ : untpd.Closure => typedBlockStats(tree.stats) + case _ => withoutMode(Mode.Precise){ typedBlockStats(tree.stats) } } var expr1 = typedExpr(tree.expr, pt.dropIfProto)(using exprCtx) @@ -1913,7 +1916,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree = { - val (bindings1, exprCtx) = typedBlockStats(tree.bindings) + val (bindings1, exprCtx) = withoutMode(Mode.Precise){ typedBlockStats(tree.bindings) } val expansion1 = typed(tree.expansion, pt)(using inlineContext(tree.call)(using exprCtx)) assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1), bindings1, expansion1) @@ -2686,7 +2689,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted() var stats1 = typedStats(tree.stats, pkg.moduleClass)._1 if (!ctx.isAfterTyper) - stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1 + stats1 = stats1 ++ withoutMode(Mode.Precise){ typedBlockStats(MainProxies.proxies(stats1))._1 } cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef) } case _ => @@ -3047,7 +3050,48 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typed(tree, pt, locked)(using ctx.withSource(tree.source)) else if ctx.run.nn.isCancelled then tree.withType(WildcardType) - else adapt(typedUnadapted(tree, pt, locked), pt, locked) + else + val tu = typedUnadapted(tree, pt, locked) + pt match + // If we are already in precise mode, then the arguments are already typed precisely, + // so there is no need for any additional logic. + case pt : FunProto if !ctx.mode.is(Mode.Precise) => + extension (tpe: Type) def getArgsPrecises: List[Boolean] = tpe.widen match + case mt: MethodType => mt.paramInfos.map(_.isPrecise) + case pt: PolyType => pt.resType.getArgsPrecises + case _ => Nil + val argsPrecises = tu.tpe.getArgsPrecises + // if the function arguments are known to be precise, then we update the + // proto with this information and later propagate its state back to the + // original proto. + if (argsPrecises.contains(true)) + val ptPrecises = pt.derivedFunProto(argsPrecises = argsPrecises) + val adapted = adapt(tu, ptPrecises, locked) + pt.resetTo(ptPrecises.snapshot) + adapted + // otherwise, we need to check for overloaded function, because in that + // case we may not know if the final adapted function will be precise. + else tu.tpe match + // the function is overloaded, so we preserve the typer and proto state, + // adapt the tree, and then check if the arguments should be considered as + // precise. if so, then we need to recover the typer state and proto + // state, and re-adapt the tree with the precise enforcement. + case v: TermRef if v.isOverloaded => + val savedTyperState = ctx.typerState.snapshot() + val savedProtoState = pt.snapshot + val adaptedMaybePrecise = adapt(tu, pt, locked) + val argsPrecises = adaptedMaybePrecise.tpe.getArgsPrecises + if (argsPrecises.contains(true)) + val ptPrecises = pt.derivedFunProto(argsPrecises = argsPrecises) + ctx.typerState.resetTo(savedTyperState) + pt.resetTo(savedProtoState) + val adapted = adapt(tu, ptPrecises, locked) + pt.resetTo(ptPrecises.snapshot) + adapted + else adaptedMaybePrecise + // the function is not overloaded or has precise arguments + case _ => adapt(tu, pt, locked) + case _ => adapt(tu, pt, locked) } def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = From 57ac4605b78c5255f364001e062853ed24fc0c3b Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:27:46 -0400 Subject: [PATCH 10/17] add support for precise typing with implicit conversions. for this purpose, additional context was required in `TyperState`. a stack was used for the conversion history. Also, a more general fix for #12802 was applied. --- .../dotty/tools/dotc/core/TyperState.scala | 30 +++++++++++-- .../dotty/tools/dotc/typer/Applications.scala | 43 +++++++++++++++++-- .../dotty/tools/dotc/typer/Implicits.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 23 ++++++++++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index d2df2a2aebef..8db47c0368d8 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -12,6 +12,7 @@ import collection.mutable import java.lang.ref.WeakReference import util.{Stats, SimpleIdentityMap} import Decorators._ +import ast.tpd.Tree import scala.annotation.internal.sharable @@ -24,21 +25,23 @@ object TyperState { .setCommittable(true) type LevelMap = SimpleIdentityMap[TypeVar, Integer] + type PreciseConversionStack = List[Set[Tree]] - opaque type Snapshot = (Constraint, TypeVars, LevelMap) + opaque type Snapshot = (Constraint, TypeVars, LevelMap, PreciseConversionStack) extension (ts: TyperState) def snapshot()(using Context): Snapshot = - (ts.constraint, ts.ownedVars, ts.upLevels) + (ts.constraint, ts.ownedVars, ts.upLevels, ts.myPreciseConvStack) def resetTo(state: Snapshot)(using Context): Unit = - val (constraint, ownedVars, upLevels) = state + val (constraint, ownedVars, upLevels, myPreciseConvStack) = state for tv <- ownedVars do if !ts.ownedVars.contains(tv) then // tv has been instantiated tv.resetInst(ts) ts.constraint = constraint ts.ownedVars = ownedVars ts.upLevels = upLevels + ts.myPreciseConvStack = myPreciseConvStack } class TyperState() { @@ -93,6 +96,23 @@ class TyperState() { private var upLevels: LevelMap = _ + // Stack can be empty and precise conversion occur in `val x : Foo = from` + // where `from` is implicitly and precisely converted into `Foo`. We don't + // care about these conversions. + private var myPreciseConvStack: List[Set[Tree]] = _ + def hasPreciseConversion(tree: Tree): Boolean = + myPreciseConvStack match + case head :: _ => head.contains(tree) + case _ => false + def addPreciseConversion(tree: Tree): Unit = + myPreciseConvStack = myPreciseConvStack match + case head :: tail => (head + tree) :: tail + case _ => myPreciseConvStack + def pushPreciseConversionStack(): Unit = + myPreciseConvStack = Set.empty[Tree] :: myPreciseConvStack + def popPreciseConversionStack(): Unit = + myPreciseConvStack = myPreciseConvStack.drop(1) + /** Initializes all fields except reporter, isCommittable, which need to be * set separately. */ @@ -105,6 +125,7 @@ class TyperState() { this.myOwnedVars = SimpleIdentitySet.empty this.upLevels = SimpleIdentityMap.empty this.isCommitted = false + this.myPreciseConvStack = Nil this /** A fresh typer state with the same constraint as this one. */ @@ -115,6 +136,7 @@ class TyperState() { .setReporter(reporter) .setCommittable(committable) ts.upLevels = upLevels + ts.myPreciseConvStack = myPreciseConvStack ts /** The uninstantiated variables */ @@ -162,6 +184,8 @@ class TyperState() { assert(!isCommitted, s"$this is already committed") val targetState = ctx.typerState + targetState.myPreciseConvStack = myPreciseConvStack + val nothingToCommit = (constraint eq targetState.constraint) && !reporter.hasUnreportedMessages assert(!targetState.isCommitted || nothingToCommit || // Committing into an already committed TyperState usually doesn't make diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a8cf22cf773a..9a2b15709f8a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -891,7 +891,7 @@ trait Applications extends Compatibility { */ def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = { - def realApply(using Context): Tree = { + def realApply(argsPrecises: List[Boolean] = Nil)(using Context): Tree = { val resultProto = tree.fun match case Select(New(tpt), _) if pt.isInstanceOf[ValueType] => if tpt.isType && typedAheadType(tpt).tpe.typeSymbol.typeParams.isEmpty then @@ -905,7 +905,7 @@ trait Applications extends Compatibility { // Do ignore other expected result types, since there might be an implicit conversion // on the result. We could drop this if we disallow unrestricted implicit conversions. val originalProto = - new FunProto(tree.args, resultProto)(this, tree.applyKind)(using argCtx(tree)) + new FunProto(tree.args, resultProto)(this, tree.applyKind, argsPrecises = argsPrecises)(using argCtx(tree)) record("typedApply") val fun1 = typedExpr(tree.fun, originalProto) @@ -1015,6 +1015,41 @@ trait Applications extends Compatibility { } } + /** Attempts to run `realApply`, and if implicit conversions should be + * precise, then re-run `realApply` with the precise enforcement. + */ + def realApplyWithRetry(using Context): Tree = { + ctx.typerState.pushPreciseConversionStack() + val firstAttemptCtx = ctx.fresh.setNewTyperState() + // tuple application for tuple return type gets special treatment when arguments are precise + val argsPrecises = (tree.fun, pt.dealias) match + case (untpd.TypedSplice(fun), AppliedType(_, args)) + if defn.isTupleClass(fun.tpe.classSymbol.companionClass) && defn.isTupleNType(pt) => + args.map(_.isPrecise) + case _ => Nil + val app = realApply(argsPrecises)(using firstAttemptCtx) + val retTree = app match + // If we are already in precise mode, then the arguments are already typed precisely, + // so there is no need for any additional logic. + case Apply(_, args) if !ctx.mode.is(Mode.Precise) => + val convArgsPrecises = args.map(firstAttemptCtx.typerState.hasPreciseConversion) + if (convArgsPrecises.contains(true)) + val preciseCtx = ctx.fresh.setNewTyperState() + val updatedPrecises = + if (argsPrecises.nonEmpty) convArgsPrecises.lazyZip(argsPrecises).map(_ || _) + else convArgsPrecises + val app = realApply(updatedPrecises)(using preciseCtx) + preciseCtx.typerState.commit() + app + else + firstAttemptCtx.typerState.commit() + app + case _ => + firstAttemptCtx.typerState.commit() + app + ctx.typerState.popPreciseConversionStack() + retTree + } /** Convert expression like * * e += (args) @@ -1037,7 +1072,7 @@ trait Applications extends Compatibility { val app1 = if (untpd.isOpAssign(tree)) tryEither { - realApply + realApplyWithRetry } { (failedVal, failedState) => tryEither { typedOpAssign @@ -1049,7 +1084,7 @@ trait Applications extends Compatibility { else { val app = tree.fun match case _: untpd.Splice if ctx.mode.is(Mode.QuotedPattern) => typedAppliedSplice(tree, pt) - case _ => realApply + case _ => realApplyWithRetry app match { case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => val op = fn.symbol diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ac86702ac588..6b71f1d6242c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1013,6 +1013,7 @@ trait Implicits: // would cause a cyclic reference error (if the import is named) or cause a // spurious import skip (if the import is a wildcard import). See i12802 for a test case. var searchCtx = ctx + while searchCtx.outer.owner.isImport do searchCtx = searchCtx.outer if ctx.owner.isImport then while searchCtx = searchCtx.outer diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 533212f02e2d..4d909a548f69 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4019,6 +4019,29 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case SearchSuccess(found, _, _, isExtension) => if isExtension then found else + object Extract: + @tailrec def unapply(tree: Tree): Option[Tree] = + tree match + case Apply(tree, _) => unapply(tree) + case Select(tree, _) => unapply(tree) + case Inlined(tree, _, _) => unapply(tree) + case ta: TypeApply => Some(ta.fun) + case _ => None + found match + case Extract(fun) => + fun.tpe.widen match + case pt: PolyType => pt.resType match + case mt: MethodType => mt.paramInfos.headOption.foreach { + case v if v.isPrecise => + ctx.typerState.addPreciseConversion(found) + case _ => + } + case AppliedType(tycon, from :: _) + if tycon.derivesFrom(defn.ConversionClass) && from.isPrecise => + ctx.typerState.addPreciseConversion(found) + case _ => + case _ => + case _ => checkImplicitConversionUseOK(found) withoutMode(Mode.ImplicitsEnabled)(readapt(found)) case failure: SearchFailure => From d2c6d9de32df3513a71b5e7546401bb41fadd20f Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:45:02 -0400 Subject: [PATCH 11/17] fix test check file that its order was changed (because of retyping of precise implicit conversions, the mechanism changed slightly so it caused the reported to change to error order) --- tests/neg-custom-args/fatal-warnings/i9408a.check | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/neg-custom-args/fatal-warnings/i9408a.check b/tests/neg-custom-args/fatal-warnings/i9408a.check index ce2f8c4edd15..fc9223faf435 100644 --- a/tests/neg-custom-args/fatal-warnings/i9408a.check +++ b/tests/neg-custom-args/fatal-warnings/i9408a.check @@ -1,3 +1,7 @@ +-- Error: tests/neg-custom-args/fatal-warnings/i9408a.scala:59:2 ------------------------------------------------------- +59 | 123.foo // error + | ^^^ + |The conversion (Test11.a2foo : [A]: A => Test11.Foo) will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views. -- Error: tests/neg-custom-args/fatal-warnings/i9408a.scala:16:20 ------------------------------------------------------ 16 | val length: Int = "qwerty" // error | ^^^^^^^^ @@ -17,8 +21,4 @@ -- Error: tests/neg-custom-args/fatal-warnings/i9408a.scala:35:60 ------------------------------------------------------ 35 | implicit def a2int[A](a: A)(implicit ev: A => Int): Int = a // error | ^ - |The conversion (ev : A => Int) will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views. --- Error: tests/neg-custom-args/fatal-warnings/i9408a.scala:59:2 ------------------------------------------------------- -59 | 123.foo // error - | ^^^ - |The conversion (Test11.a2foo : [A]: A => Test11.Foo) will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views. + |The conversion (ev : A => Int) will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views. \ No newline at end of file From 190cd318aecd028dadf1066513e5e76a67b39b94 Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:29:43 -0400 Subject: [PATCH 12/17] add support for precise extension methods and implicit classes --- .../src/dotty/tools/dotc/typer/Typer.scala | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4d909a548f69..85c160e54f19 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -646,10 +646,46 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { record("typedSelect") - def typeSelectOnTerm(using Context): Tree = - val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) + def typeSelectOnTerm(precise: Boolean)(using Context): Tree = + val preciseMode = if (precise) Mode.Precise else Mode.None + val qual = withMode(preciseMode){ typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) } typedSelect(tree, pt, qual).withSpan(tree.span).computeNullable() + /** Attempts to do the type select, but if this is an extension method or implicit class + * and the qualifier is an argument that needs to be precise, then we need to rerun the + * type select with precise enforcement on. + */ + def typeSelectOnTermWithPreciseRetry(using Context): Tree = + val firstAttemptCtx = ctx.fresh.setNewTyperState() + val selected = typeSelectOnTerm(precise = false)(using firstAttemptCtx) + object Extract: + @tailrec def unapply(tree: Tree): Option[Apply] = + tree match + // extension method with arguments + case TypeApply(fun: Apply, _) => Some(fun) + // extension method with no arguments + case apply : Apply => Some(apply) + // deeper nesting or a result of implicit classes + case Select(tree, _) => unapply(tree) + case _ => None + selected match + // `fun` may not be typed due to errors like ambiguity in typing, so we check for that. + // If we are already in precise mode, then the qualifier is already typed precisely, + // so there is no need for any additional logic. + case Extract(Apply(TypeApply(fun, _), _)) if fun.hasType && !ctx.mode.is(Mode.Precise) => + fun.tpe.widen match + case pt: PolyType if pt.paramPrecises.headOption.contains(true) => + val preciseCtx = ctx.fresh.setNewTyperState() + val selected = typeSelectOnTerm(precise = true)(using preciseCtx) + preciseCtx.typerState.commit() + selected + case _ => + firstAttemptCtx.typerState.commit() + selected + case _ => + firstAttemptCtx.typerState.commit() + selected + def javaSelectOnType(qual: Tree)(using Context) = // semantic name conversion for `O$` in java code if !qual.symbol.is(JavaDefined) then @@ -674,7 +710,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def selectWithFallback(fallBack: Context ?=> Tree) = - tryAlternatively(typeSelectOnTerm)(fallBack) + tryAlternatively(typeSelectOnTermWithPreciseRetry)(fallBack) if (tree.qualifier.isType) { val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) @@ -685,7 +721,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // value A and from the type A. We have to try both. selectWithFallback(tryJavaSelectOnType) // !!! possibly exponential bcs of qualifier retyping else - typeSelectOnTerm + typeSelectOnTermWithPreciseRetry } def typedThis(tree: untpd.This)(using Context): Tree = { From b433244470a989dd4fc5547e70d6203e37285cb8 Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:42:38 -0400 Subject: [PATCH 13/17] add support for precise default arguments --- .../src/dotty/tools/dotc/typer/Namer.scala | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 7dfbd9c9c59b..013dca38e4ed 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1855,19 +1855,18 @@ class Namer { typer: Typer => case _ => approxTp - var rhsCtx = ctx.fresh.addMode(Mode.InferringReturnType) - if sym.isInlineMethod then rhsCtx = rhsCtx.addMode(Mode.InlineableBody) - if sym.is(ExtensionMethod) then rhsCtx = rhsCtx.addMode(Mode.InExtensionMethod) - val typeParams = paramss.collect { case TypeSymbols(tparams) => tparams }.flatten - if (typeParams.nonEmpty) { - // we'll be typing an expression from a polymorphic definition's body, - // so we must allow constraining its type parameters - // compare with typedDefDef, see tests/pos/gadt-inference.scala - rhsCtx.setFreshGADTBounds - rhsCtx.gadt.addToConstraint(typeParams) - } - - def typedAheadRhs(pt: Type) = + def typedAheadRhs(pt: Type)(using Context) = + var rhsCtx = ctx.fresh.addMode(Mode.InferringReturnType) + if sym.isInlineMethod then rhsCtx = rhsCtx.addMode(Mode.InlineableBody) + if sym.is(ExtensionMethod) then rhsCtx = rhsCtx.addMode(Mode.InExtensionMethod) + val typeParams = paramss.collect { case TypeSymbols(tparams) => tparams }.flatten + if (typeParams.nonEmpty) { + // we'll be typing an expression from a polymorphic definition's body, + // so we must allow constraining its type parameters + // compare with typedDefDef, see tests/pos/gadt-inference.scala + rhsCtx.setFreshGADTBounds + rhsCtx.gadt.addToConstraint(typeParams) + } PrepareInlineable.dropInlineIfError(sym, typedAheadExpr(mdef.rhs, pt)(using rhsCtx)) @@ -1877,7 +1876,8 @@ class Namer { typer: Typer => // parameters like in `def mkList[T](value: T = 1): List[T]`. val defaultTp = defaultParamType val pt = inherited.orElse(expectedDefaultArgType).orElse(fallbackProto).widenExpr - val tp = typedAheadRhs(pt).tpe + val preciseMode = if (defaultTp.isPrecise) Mode.Precise else Mode.None + val tp = withMode(preciseMode){ typedAheadRhs(pt) }.tpe if (defaultTp eq pt) && (tp frozen_<:< defaultTp) then // When possible, widen to the default getter parameter type to permit a // larger choice of overrides (see `default-getter.scala`). From 7ebeab97cbfa92013c424741d4e1ea4bb347fe13 Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:37:38 -0400 Subject: [PATCH 14/17] add checks for proper precise aliasing, extending, and overriding --- .../dotty/tools/dotc/core/TypeComparer.scala | 6 +-- .../tools/dotc/transform/PostTyper.scala | 5 +- .../tools/dotc/typer/PreciseChecker.scala | 49 +++++++++++++++++++ .../dotty/tools/dotc/typer/RefChecks.scala | 10 ++++ 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/PreciseChecker.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b85f72424566..f2d41f73899a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -22,7 +22,7 @@ import scala.util.control.NonFatal import typer.ProtoTypes.constrained import typer.Applications.productSelectorTypes import reporting.trace -import annotation.constructorOnly +import annotation.{constructorOnly, tailrec} import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam} /** Provides methods to compare types. @@ -2103,7 +2103,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * to override `tp2` ? This is the case if they're pairwise >:>. */ def matchingPolyParams(tp1: PolyType, tp2: PolyType): Boolean = { - def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match { + @tailrec def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match { case formal1 :: rest1 => formals2 match { case formal2 :: rest2 => @@ -2116,7 +2116,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case nil => formals2.isEmpty } - loop(tp1.paramInfos, tp2.paramInfos) + tp1.paramPrecises == tp2.paramPrecises && loop(tp1.paramInfos, tp2.paramInfos) } // Type equality =:= diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3db751df4145..18218539835f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -6,7 +6,7 @@ import scala.collection.mutable import core._ import dotty.tools.dotc.typer.Checking import dotty.tools.dotc.inlines.Inlines -import dotty.tools.dotc.typer.VarianceChecker +import dotty.tools.dotc.typer.{VarianceChecker, PreciseChecker} import typer.ErrorReporting.errorTree import Types._, Contexts._, Names._, Flags._, DenotTransformers._, Phases._ import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ @@ -388,6 +388,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase annotateExperimental(sym) tree.rhs match case impl: Template => + if (!sym.is(Flags.Given)) // skipping over given classes that are generated from `given ... with {}` + PreciseChecker.checkClass(impl) for parent <- impl.parents do Checking.checkTraitInheritance(parent.tpe.classSymbol, sym.asClass, parent.srcPos) // Add SourceFile annotation to top-level classes @@ -403,6 +405,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase Checking.checkGoodBounds(tree.symbol) (tree.rhs, sym.info) match case (rhs: LambdaTypeTree, bounds: TypeBounds) => + PreciseChecker.checkLambda(rhs, sym.isOpaqueAlias) VarianceChecker.checkLambda(rhs, bounds) if sym.isOpaqueAlias then VarianceChecker.checkLambda(rhs, TypeBounds.upper(sym.opaqueAlias)) diff --git a/compiler/src/dotty/tools/dotc/typer/PreciseChecker.scala b/compiler/src/dotty/tools/dotc/typer/PreciseChecker.scala new file mode 100644 index 000000000000..1a890272c115 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/PreciseChecker.scala @@ -0,0 +1,49 @@ +package dotty.tools.dotc +package typer + +import dotty.tools.dotc.ast.{ Trees, tpd } +import core.* +import Types.*, Contexts.*, Trees.* +import Decorators.* + +object PreciseChecker: + enum Mode: + case SamePrecise, AllowMorePrecise, AllowLessPrecise + def check(tparams: List[tpd.TypeDef], applied: Type, mode : Mode)(using Context): Unit = + applied match + case tpe@AppliedType(_, args) => + args.foreach(check(tparams, _, mode)) //recursively checking applied params + val appliedParamPreciseList = tpe.tyconTypeParams.map(_.paramPrecise) + val tdefParamPreciseMap = tparams.view.map(p => (p.name, p.symbol.paramPrecise)).toMap + + def label(precise: Boolean): String = if (precise) "precise" else "imprecise" + args.view.zipWithIndex.foreach { + case (a: TypeRef, i) if a.symbol.name.isTypeName => + val paramName = a.symbol.name.asTypeName + val appliedParamPrecise = appliedParamPreciseList(i) + tdefParamPreciseMap.get(paramName).foreach { tdefParamPrecise => + val preciseMismatch = mode match + case Mode.SamePrecise => tdefParamPrecise != appliedParamPrecise + case Mode.AllowMorePrecise => !tdefParamPrecise && appliedParamPrecise + case Mode.AllowLessPrecise => tdefParamPrecise && !appliedParamPrecise + if preciseMismatch then + val pos = tparams.find(_.name == paramName).get.srcPos + report.error(em"${label(tdefParamPrecise)} type parameter $paramName occurs in ${label(appliedParamPrecise)} position in $tpe", pos) + } + case _ => + } + case _ => + + def checkClass(tree: tpd.Template)(using Context): Unit = + val tparams = tree.constr.leadingTypeParams + tree.parents.view.map(_.tpe.dealias).foreach(check(tparams, _, Mode.AllowMorePrecise)) + + def checkLambda(tree: tpd.LambdaTypeTree, isOpaque: Boolean)(using Context): Unit = + tree.body.tpe.dealiasKeepOpaques match + case at: AppliedType => + val mode = if (isOpaque) Mode.SamePrecise else Mode.AllowLessPrecise + check(tree.tparams, at, mode) + case tb: TypeBounds => + check(tree.tparams, tb.hi, Mode.AllowMorePrecise) + check(tree.tparams, tb.lo, Mode.AllowLessPrecise) + case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 94eacca5c7db..77077fba5bd9 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -409,6 +409,14 @@ object RefChecks { !(syms1 exists (set2 contains _)) } + def samePrecises(memberTp: Type, otherTp: Type): Boolean = + (memberTp, otherTp) match + case (mpt: PolyType, otp: PolyType) => + mpt.paramPrecises == otp.paramPrecises + case (TypeBounds(_, mptHi: TypeLambda), TypeBounds(_, otpHi: TypeLambda)) => + mptHi.paramPrecises == otpHi.paramPrecises + case _ => true + // o: public | protected | package-protected (aka java's default access) // ^-may be overridden by member with access privileges-v // m: public | public/protected | public/protected/package-protected-in-same-package-as-o @@ -509,6 +517,8 @@ object RefChecks { else if (!compatTypes(memberTp(self), otherTp(self)) && !compatTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) overrideError("has incompatible type", compareTypes = true) + else if (!samePrecises(memberTp(self), otherTp(self))) + overrideError("has different precise type parameter annotations") else if (member.targetName != other.targetName) if (other.targetName != other.name) overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match") From db9076e41ab530b85c5cb3195ffa682ff8dfc46b Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:39:39 -0400 Subject: [PATCH 15/17] preserves opaque types when widening singletons, to keep extra precise information that the opaque may have. E.g.: `opaque type Foo[@precise T] = T` --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ee0cda5a287f..b3ca0cb0a695 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1315,7 +1315,7 @@ object Types { * and going to the operands of & and |. * Overridden and cached in OrType. */ - def widenSingletons(using Context): Type = dealias match { + def widenSingletons(using Context): Type = dealiasKeepOpaques match { case tp: SingletonType => tp.widen case tp: OrType => From 00a4998172c239271091931e8e2e09955ce7422a Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 17:41:31 -0400 Subject: [PATCH 16/17] fix hidden compiletime-ops bug that only surfaces when a covariant precise type is used with compiletime-ops (the reason is that default covariant widening kept this bug from surfacing) --- compiler/src/dotty/tools/dotc/core/TypeEval.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/TypeEval.scala b/compiler/src/dotty/tools/dotc/core/TypeEval.scala index 7ec0f12db3b6..725d7824ab40 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeEval.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeEval.scala @@ -13,6 +13,8 @@ object TypeEval: case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) => extension (tp: Type) def fixForEvaluation: Type = tp.normalized.dealias match + // deeper evaluation required + case tp : AppliedType => tryCompiletimeConstantFold(tp) // enable operations for constant singleton terms. E.g.: // ``` // final val one = 1 From 1824ad827f620b6946d56fcbeeeb5a1fe340f26c Mon Sep 17 00:00:00 2001 From: oronpo Date: Thu, 8 Sep 2022 19:06:15 -0400 Subject: [PATCH 17/17] add support for precise implicit summoning --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 85c160e54f19..60299f03ffe5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3520,7 +3520,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def methodStr = err.refStr(methPart(tree).tpe) def readapt(tree: Tree)(using Context) = adapt(tree, pt, locked) - def readaptSimplified(tree: Tree)(using Context) = readapt(simplify(tree, pt, locked)) + def readaptSimplified(tree: Tree)(using Context) = readapt( + // also locking precise type variables to prevent their widening to bounds in implicits + simplify(tree, pt, locked ++ ctx.typerState.ownedVars.filter(_.isPrecise)) + ) def missingArgs(mt: MethodType) = ErrorReporting.missingArgs(tree, mt)