Skip to content

Commit 8ec5b35

Browse files
mbovelsoronpo
authored andcommitted
[Experiment] Precise annotation
1 parent 634c580 commit 8ec5b35

File tree

6 files changed

+201
-2
lines changed

6 files changed

+201
-2
lines changed

compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import config.Printers.typr
1212
import typer.ProtoTypes.{newTypeVar, representedParamRef}
1313
import UnificationDirection.*
1414
import NameKinds.AvoidNameKind
15-
15+
import annotation.tailrec
1616
/** Methods for adding constraints and solving them.
1717
*
1818
* What goes into a Constraint as opposed to a ConstrainHandler?
@@ -545,8 +545,46 @@ trait ConstraintHandling {
545545
case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi)
546546
case _ => isSubTypeWhenFrozen(tp, defn.SingletonType)
547547

548+
def derivesPreciseAnnot(tp: Type): Boolean =
549+
tp.derivesAnnotWith(_.matches(defn.PreciseAnnot))
550+
551+
def dependsOnParam(tp: Type, param: TypeParamRef) : Boolean =
552+
tp match
553+
case v: TypeVar => v.origin == param
554+
case AppliedType(_, args) => args.exists(dependsOnParam(_, param))
555+
case _ => false
556+
557+
def isPreciseRecur(tp: Type, uninstParams : List[TypeParamRef]): Boolean = tp match
558+
//the type parameter is annotated as precise
559+
case param: TypeParamRef if derivesPreciseAnnot(param) => true
560+
case param: TypeParamRef =>
561+
//the type parameter is from an applied type and there it is annotated as precise
562+
val preciseTypeParam = constraint.domainLambdas.view.flatMap(_.resType.paramInfoss.flatten).flatMap {
563+
case AppliedType(tycon, args) =>
564+
val preciseArgIdx = tycon.typeParams.indexWhere(t => derivesPreciseAnnot(t.paramInfo.hiBound))
565+
if (preciseArgIdx >= 0) Some(args(preciseArgIdx) == param)
566+
else None
567+
case p : TypeParamRef => if (p == param) Some(false) else None
568+
case _ => None
569+
}.headOption.getOrElse(false)
570+
if preciseTypeParam then true
571+
//the type parameter is being dependent upon a different precise type parameter
572+
else if uninstParams.nonEmpty then
573+
val nextParams = uninstParams.view.dropWhile(_ != param).drop(1).toList
574+
//getting all the dependent parameters to check if they are precise
575+
val dependent = nextParams.filter(v => dependsOnParam(constraint.entry(v).loBound, param))
576+
//TODO: THIS IS HACK THAT WORKS FOR TRIVIAL CASES
577+
if dependent.isEmpty && nextParams.nonEmpty && constraint.entry(nextParams.last).loBound == defn.NothingType then
578+
isPreciseRecur(nextParams.last, Nil)
579+
else dependent.exists(isPreciseRecur(_, nextParams))
580+
else false
581+
case _ => false
582+
583+
def isPrecise(tp: Type): Boolean =
584+
isPreciseRecur(tp, constraint.uninstVars.view.reverse.map(_.origin).toList)
585+
548586
val wideInst =
549-
if isSingleton(bound) then inst
587+
if isSingleton(bound) || isPrecise(bound) then inst
550588
else dropTransparentTraits(widenIrreducible(widenOr(widenSingle(inst))), bound)
551589
wideInst match
552590
case wideInst: TypeRef if wideInst.symbol.is(Module) =>
@@ -562,6 +600,8 @@ trait ConstraintHandling {
562600
* is also a singleton type.
563601
*/
564602
def instanceType(param: TypeParamRef, fromBelow: Boolean)(using Context): Type = {
603+
// println("============================")
604+
// println(constraint.show)
565605
val approx = approximation(param, fromBelow).simplified
566606
if fromBelow then
567607
val widened = widenInferred(approx, param)

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,7 @@ class Definitions {
962962
@tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn")
963963
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
964964
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
965+
@tu lazy val PreciseAnnot: ClassSymbol = requiredClass("scala.annotation.precise")
965966
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")
966967
@tu lazy val SourceFileAnnot: ClassSymbol = requiredClass("scala.annotation.internal.SourceFile")
967968
@tu lazy val ScalaSignatureAnnot: ClassSymbol = requiredClass("scala.reflect.ScalaSignature")

compiler/src/dotty/tools/dotc/core/TypeEval.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ object TypeEval:
1313
case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) =>
1414
extension (tp: Type) def fixForEvaluation: Type =
1515
tp.normalized.dealias match
16+
// deeper evaluation required
17+
case tp : AppliedType => tryCompiletimeConstantFold(tp)
1618
// enable operations for constant singleton terms. E.g.:
1719
// ```
1820
// final val one = 1
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package scala.annotation
2+
3+
import scala.annotation.Annotation
4+
5+
@experimental
6+
class precise extends Annotation {
7+
8+
}

project/MiMaFilters.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,8 @@ object MiMaFilters {
3030
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"),
3131

3232
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.since"),
33+
34+
// APIs will be added in 3.3.0
35+
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.precise"),
3336
)
3437
}

tests/neg/precise-type-params.scala

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import annotation.precise
2+
import language.implicitConversions
3+
4+
object preciseDefs:
5+
def id[T <: Any@precise](x: T): T = x
6+
class Box[T]
7+
def idBox[T <: Any@precise](t: T): Box[T] = ???
8+
9+
final val x = id(3)
10+
val xTest: 3 = x
11+
12+
final val x3 = id(id(id(3)))
13+
val x3Test: 3 = x3
14+
15+
final val tpl = id((1, 2))
16+
val tplTest: (1, 2) = tpl
17+
18+
final val tpl2 = id((1, 2, (3, 4)))
19+
val tpl2Test: (1, 2, (3, 4)) = tpl2
20+
21+
final val list1 = id(1 :: Nil)
22+
val list1Test: List[1] = list1
23+
24+
final val list1hi = id(1 :: "hi" :: Nil)
25+
val list1hiTest: List[1 | "hi"] = list1hi
26+
27+
val c : Boolean = ???
28+
val ifVal = idBox(if c then 2 else 3)
29+
val ifValTest: Box[2 | 3] = ifVal
30+
31+
def same[T <: Any@precise](x: T, y: T) : Box[T] = ???
32+
final val sameVal = same(1, 2)
33+
val sameValTest: Box[1 | 2] = sameVal
34+
35+
def sameVarArgs[T <: Any@precise](a: T*) : Box[T] = ???
36+
final val sameVal123hi = sameVarArgs(1, 2, 3, "hi")
37+
val sameVal123hiTest: Box[1 | 2 | 3 | "hi"] = sameVal123hi
38+
39+
def dep1[T1 <: Any@precise, T2 <: T1](t1: T1)(t2: T2): Box[T1] = ???
40+
val d1 = dep1(1)(2) //error
41+
42+
def dep2[T1 <: Any, T2 <: T1@precise](t1: T1)(t2: T2): Box[T1] = ???
43+
final val d2 = dep2(1)(2)
44+
val d2Test: Box[Int] = d2
45+
46+
def dep12[T1 <: Any@precise, T2 <: T1@precise](t1: T1)(t2: T2): Box[T1] = ???
47+
final val d12 = dep12(1)(2)
48+
val d12Test: Box[1 | 2] = d12
49+
50+
class PreciseBox[T <: Any@precise]
51+
given [T]: PreciseBox[T] with {}
52+
53+
def check[T](t: T)(using PreciseBox[T]): Box[T] = ???
54+
val c1 = check(1)
55+
val c1Test: Box[Int] = c1
56+
57+
58+
object preciseInvariance:
59+
class Box[T <: Any @precise](x: T)
60+
val c: Boolean = ???
61+
val b = Box(if c then 2 else 3)
62+
val bTest: Box[2 | 3] = b
63+
final val b3 = Box(Box(Box(3)))
64+
val b3Test: Box[Box[Box[3]]] = b3
65+
66+
final val tpl = Box((1, (2, 3), "hi"))
67+
val tplTest: Box[(1, (2, 3), "hi")] = tpl
68+
69+
final val tpl2: (1, (2, 3), "hi") = (1, (2, 3), "hi")
70+
final val tpl3 = Box(tpl2)
71+
val tpl3Test: Box[tpl2.type] = tpl3
72+
73+
implicit def toBox[T <: Any @precise](from: T): Box[T] = Box(from)
74+
def box[T](b: Box[T]): Box[T] = b
75+
76+
final val btpl = box(((1, 2), (3, 4)))
77+
val btplTest: Box[((1, 2), (3, 4))] = btpl
78+
79+
final val b1 = box(1)
80+
val b1Test: Box[1] = b1
81+
82+
class Boxx[T <: Any@precise](x: T*)
83+
val b123 = Boxx(1, 2, 3)
84+
val b123Test: Boxx[1 | 2 | 3] = b123
85+
86+
87+
object preciseCovariance:
88+
class Box[+A <: Any@precise](x: A)
89+
def fromBox[B <: Any](x: Box[B]): Box[B] = x
90+
final val b1 = Box(1)
91+
val b11 = fromBox(b1)
92+
val b11Test: Box[1] = b11
93+
val b11CovTest: Box[Int] = b11
94+
95+
class Inv[A, B]
96+
class BoxCI[+C <: Any@precise, +I](c: C, i: I)
97+
def fromBoxCI[C, I](x: BoxCI[C, I]): Inv[C, I] = ???
98+
val bci = BoxCI(1, 2)
99+
val bciTest: BoxCI[1, Int] = bci
100+
val fbci = fromBoxCI(bci)
101+
val fbciTest: Inv[1, Int] = fbci
102+
val fbci12 = fromBoxCI(??? : BoxCI[1, 2])
103+
val fbci12Test: Inv[1, Int] = fbci12
104+
105+
class BoxIC[+I, +C <: Any@precise](i: I, c: C)
106+
def fromBoxIC[I, C](x: BoxIC[I, C]): Inv[I, C] = ???
107+
val bic = BoxIC(1, 2)
108+
val bicTest: BoxIC[Int, 2] = bic
109+
val fbic = fromBoxIC(bic)
110+
val fbicTest: Inv[Int, 2] = fbic
111+
val fbic12 = fromBoxIC(??? : BoxIC[1, 2])
112+
val fbic12Test: Inv[Int, 2] = fbic12
113+
114+
115+
object preciseCovarianceWithCompiletimeOps:
116+
import compiletime.ops.int.+
117+
class Inlined[+T <: Int @precise]
118+
extension [T <: Int](lhs: Inlined[T])
119+
def inc : Inlined[T + 1] = ???
120+
121+
val i1 = Inlined[1]
122+
val i3: Inlined[3] = i1.inc.inc
123+
124+
125+
object preciseEquals:
126+
class Box[T]
127+
class Foo:
128+
def ==[R <: Any@precise](r: R): Box[R] = ???
129+
130+
val f = new Foo
131+
val x = f == ((1, 2), 3)
132+
val xTest: Box[((1, 2), 3)] = x
133+
134+
135+
/*
136+
errors we are not supposed to have
137+
*/
138+
object problems:
139+
class Box[T]
140+
def idBox[T <: Any@precise](t: T): Box[T] = ???
141+
def idBoxBox[BB](x: Box[BB]): Box[BB] = x
142+
143+
val b1 = idBoxBox(idBox(1))
144+
val b1Test: Box[1] = b1
145+

0 commit comments

Comments
 (0)