Skip to content

Commit 249a87a

Browse files
mbovelsoronpo
authored andcommitted
Precise annotation SIP
1 parent bf03086 commit 249a87a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1079
-130
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,16 @@ object desugar {
256256
evidenceParamBuf.toList)
257257
end elimContextBounds
258258

259+
def isPreciseAnnot(tree: untpd.Tree)(using Context): Boolean =
260+
tree match
261+
case Apply(Select(New(clsSel), _), Nil) =>
262+
inContext(ctx.fresh.setReporter(Reporter.NoReporter).setExploreTyperState()) {
263+
try
264+
ctx.typer.typedExpr(clsSel).tpe.classSymbol == defn.PreciseAnnot
265+
catch case _ : Throwable => false
266+
}
267+
case _ => false
268+
259269
def addDefaultGetters(meth: DefDef)(using Context): Tree =
260270

261271
/** The longest prefix of parameter lists in paramss whose total number of
@@ -293,14 +303,33 @@ object desugar {
293303
vparam => toDefParam(vparam, keepAnnotations = true, keepDefault = false)
294304
}
295305

306+
// gets arguments should be considered precise
307+
val paramPrecises: List[Boolean] =
308+
// indication for the type parameters preciseness
309+
val preciseMap : Map[TypeName, Boolean] =
310+
meth.leadingTypeParams.map(t => (t.name, t.mods.annotations.exists(isPreciseAnnot))).toMap
311+
// mapping type parameters preciseness onto term parameter preciseness
312+
meth.termParamss.view.flatten.map(p => p.tpt).map {
313+
case Ident(n) => preciseMap.getOrElse(n.asTypeName, false)
314+
case _ => false
315+
}.toList
316+
296317
def defaultGetters(paramss: List[ParamClause], n: Int): List[DefDef] = paramss match
297318
case ValDefs(vparam :: vparams) :: paramss1 =>
298319
def defaultGetter: DefDef =
320+
val (rhs, tpt) =
321+
// if the parameter is precise, then we add explicit return type for the
322+
// definition to keep the precise type after precise typing.
323+
if paramPrecises(n) then
324+
val rhsTyped = withMode(Mode.Precise){ctx.typer.typedExpr(vparam.rhs)}
325+
(TypedSplice(rhsTyped), TypeTree(rhsTyped.tpe))
326+
// otherwise, the desugaring is unchanged from the status quo
327+
else (vparam.rhs, TypeTree())
299328
DefDef(
300329
name = DefaultGetterName(meth.name, n),
301330
paramss = getterParamss(n),
302-
tpt = TypeTree(),
303-
rhs = vparam.rhs
331+
tpt = tpt,
332+
rhs = rhs
304333
)
305334
.withMods(Modifiers(
306335
meth.mods.flags & (AccessFlags | Synthetic) | (vparam.mods.flags & Inline),
@@ -381,9 +410,10 @@ object desugar {
381410

382411
@sharable private val synthetic = Modifiers(Synthetic)
383412

384-
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean): TypeDef = {
413+
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean)(using Context): TypeDef = {
385414
var mods = tparam.rawMods
386-
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
415+
val onlyPreciseAnnot = mods.annotations.filter(isPreciseAnnot)
416+
if (!keepAnnotations) mods = mods.withAnnotations(onlyPreciseAnnot)
387417
tparam.withMods(mods & EmptyFlags | Param)
388418
}
389419
private def toDefParam(vparam: ValDef, keepAnnotations: Boolean, keepDefault: Boolean): ValDef = {

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
286286
end recur
287287

288288
val (rtp, paramss) = recur(sym.info, sym.rawParamss)
289+
val typeParamsSyms = paramss.view.flatten.filter(_.isType).toList
290+
@tailrec def allPrecises(tp: Type, precises: List[Boolean]): List[Boolean] =
291+
tp match
292+
case pt : PolyType => allPrecises(pt.resType, precises ++ pt.paramPrecises)
293+
case mt : MethodType => allPrecises(mt.resType, precises)
294+
case _ => precises
295+
val paramPrecises = allPrecises(sym.info, Nil)
296+
paramPrecises.lazyZip(typeParamsSyms).foreach {
297+
case (true, p) => p.addAnnotation(defn.PreciseAnnot)
298+
case _ =>
299+
}
289300
DefDef(sym, paramss, rtp, rhsFn(paramss.nestedMap(ref)))
290301
end DefDef
291302

compiler/src/dotty/tools/dotc/cc/Setup.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ extends tpd.TreeTraverser:
405405
info match
406406
case mt: MethodOrPoly =>
407407
val psyms = psymss.head
408-
mt.companion(mt.paramNames)(
408+
mt.companion(mt.paramNames, mt.paramPrecises)(
409409
mt1 =>
410410
if !psyms.exists(_.isUpdatedAfter(preRecheckPhase)) && !mt.isParamDependent && prevLambdas.isEmpty then
411411
mt.paramInfos

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import UnificationDirection.*
1414
import NameKinds.AvoidNameKind
1515
import util.SimpleIdentitySet
1616
import NullOpsDecorator.stripNull
17-
17+
import annotation.tailrec
1818
/** Methods for adding constraints and solving them.
1919
*
2020
* What goes into a Constraint as opposed to a ConstrainHandler?
@@ -647,7 +647,8 @@ trait ConstraintHandling {
647647
case _ => isSubTypeWhenFrozen(tp, defn.SingletonType)
648648

649649
val wideInst =
650-
if isSingleton(bound) then inst
650+
//keeping the precise type if the bound is Singleton or precise or the mode is precise
651+
if isSingleton(bound) || ctx.mode.is(Mode.Precise) || bound.isPrecise then inst
651652
else dropTransparentTraits(widenIrreducible(widenOr(widenSingle(inst))), bound)
652653
wideInst match
653654
case wideInst: TypeRef if wideInst.symbol.is(Module) =>

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class Definitions {
133133
val underlyingName = name.asSimpleName.drop(6)
134134
val underlyingClass = ScalaPackageVal.requiredClass(underlyingName)
135135
denot.info = TypeAlias(
136-
HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)(
136+
HKTypeLambda(argParamNames :+ "R".toTypeName, Nil, argVariances :+ Covariant)(
137137
tl => List.fill(arity + 1)(TypeBounds.empty),
138138
tl => CapturingType(underlyingClass.typeRef.appliedTo(tl.paramRefs),
139139
CaptureSet.universal)
@@ -187,7 +187,7 @@ class Definitions {
187187
useCompleter: Boolean = false) = {
188188
val tparamNames = PolyType.syntheticParamNames(typeParamCount)
189189
val tparamInfos = tparamNames map (_ => bounds)
190-
def ptype = PolyType(tparamNames)(_ => tparamInfos, resultTypeFn)
190+
def ptype = PolyType(tparamNames, Nil)(_ => tparamInfos, resultTypeFn)
191191
val info =
192192
if (useCompleter)
193193
new LazyType {
@@ -719,7 +719,7 @@ class Definitions {
719719
case meth: MethodType =>
720720
info.derivedLambdaType(
721721
resType = meth.derivedLambdaType(
722-
paramNames = Nil, paramInfos = Nil))
722+
paramNames = Nil, paramPrecises = Nil, paramInfos = Nil))
723723
}
724724
}
725725
val argConstr = constr.copy().entered
@@ -988,6 +988,7 @@ class Definitions {
988988
@tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn")
989989
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
990990
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
991+
@tu lazy val PreciseAnnot: ClassSymbol = requiredClass("scala.annotation.precise")
991992
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")
992993
@tu lazy val SourceFileAnnot: ClassSymbol = requiredClass("scala.annotation.internal.SourceFile")
993994
@tu lazy val ScalaSignatureAnnot: ClassSymbol = requiredClass("scala.reflect.ScalaSignature")

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ object Denotations {
546546
&& tp1.isErasedMethod == tp2.isErasedMethod =>
547547
val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection)
548548
if resType.exists then
549-
tp1.derivedLambdaType(mergeParamNames(tp1, tp2), tp1.paramInfos, resType)
549+
tp1.derivedLambdaType(mergeParamNames(tp1, tp2), Nil, tp1.paramInfos, resType)
550550
else NoType
551551
case _ => NoType
552552
case tp1: PolyType =>
@@ -556,6 +556,7 @@ object Denotations {
556556
if resType.exists then
557557
tp1.derivedLambdaType(
558558
mergeParamNames(tp1, tp2),
559+
Nil,
559560
tp1.paramInfos.zipWithConserve(tp2.paramInfos)( _ & _ ),
560561
resType)
561562
else NoType

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ final class ProperGadtConstraint private(
9191
override def addToConstraint(params: List[Symbol])(using Context): Boolean = {
9292
import NameKinds.DepParamName
9393

94-
val poly1 = PolyType(params.map { sym => DepParamName.fresh(sym.name.toTypeName) })(
94+
val poly1 = PolyType(params.map { sym => DepParamName.fresh(sym.name.toTypeName) }, params.map(_.paramPrecise))(
9595
pt => params.map { param =>
9696
// In bound type `tp`, replace the symbols in dependent positions with their internal TypeParamRefs.
9797
// The replaced symbols will be later picked up in `ConstraintHandling#addToConstraint`

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ case class Mode(val bits: Int) extends AnyVal {
1111
def isExpr: Boolean = (this & PatternOrTypeBits) == None
1212

1313
override def toString: String =
14-
(0 until 31).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")")
14+
(0 until 32).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")")
1515

1616
def ==(that: Mode): Boolean = this.bits == that.bits
1717
def !=(that: Mode): Boolean = this.bits != that.bits
@@ -129,4 +129,9 @@ object Mode {
129129
* Type `Null` becomes a subtype of non-primitive value types in TypeComparer.
130130
*/
131131
val RelaxedOverriding: Mode = newMode(30, "RelaxedOverriding")
132+
133+
/**
134+
* Indication that argument widening should not take place.
135+
*/
136+
val Precise: Mode = newMode(31, "Precise")
132137
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
540540
val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos: @unchecked
541541
paramInfos = TypeBounds(lo, LazyRef.of(hi)) :: pinfos1
542542
}
543-
ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType))
543+
ensureFresh(tl.newLikeThis(tl.paramNames, tl.paramPrecises, paramInfos, tl.resultType))
544544
}
545545
else tl
546546

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ trait ParamInfo {
3838
/** The variance of the type parameter */
3939
def paramVariance(using Context): Variance
4040

41+
/** The precise enforcement indicator of the type parameter */
42+
def paramPrecise(using Context): Boolean
43+
4144
/** The variance of the type parameter, as a number -1, 0, +1.
4245
* Bivariant is mapped to 1, i.e. it is treated like Covariant.
4346
*/

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,11 @@ object SymDenotations {
14911491
else if is(Contravariant) then Contravariant
14921492
else EmptyFlags
14931493

1494+
/** The precise enforcement indicator of this type parameter or type member
1495+
*/
1496+
final def precise(using Context): Boolean =
1497+
hasAnnotation(defn.PreciseAnnot)
1498+
14941499
/** The flags to be used for a type parameter owned by this symbol.
14951500
* Overridden by ClassDenotation.
14961501
*/

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,12 @@ object Symbols {
346346
def paramInfoAsSeenFrom(pre: Type)(using Context): Type = pre.memberInfo(this)
347347
def paramInfoOrCompleter(using Context): Type = denot.infoOrCompleter
348348
def paramVariance(using Context): Variance = denot.variance
349+
def paramPrecise(using Context): Boolean =
350+
val owner = denot.owner
351+
if (owner.isConstructor)
352+
owner.owner.typeParams.exists(p => p.name == name && p.paramPrecise)
353+
else
354+
denot.precise
349355
def paramRef(using Context): TypeRef = denot.typeRef
350356

351357
// -------- Printing --------------------------------------------------------

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import scala.util.control.NonFatal
2222
import typer.ProtoTypes.constrained
2323
import typer.Applications.productSelectorTypes
2424
import reporting.trace
25-
import annotation.constructorOnly
25+
import annotation.{constructorOnly, tailrec}
2626
import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam}
2727

2828
/** Provides methods to compare types.
@@ -1097,7 +1097,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10971097
variancesConform(remainingTparams, tparams) && {
10981098
val adaptedTycon =
10991099
if d > 0 then
1100-
HKTypeLambda(remainingTparams.map(_.paramName))(
1100+
HKTypeLambda(remainingTparams.map(_.paramName), remainingTparams.map(_.paramPrecise))(
11011101
tl => remainingTparams.map(remainingTparam =>
11021102
tl.integrate(remainingTparams, remainingTparam.paramInfo).bounds),
11031103
tl => otherTycon.appliedTo(
@@ -2103,7 +2103,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
21032103
* to override `tp2` ? This is the case if they're pairwise >:>.
21042104
*/
21052105
def matchingPolyParams(tp1: PolyType, tp2: PolyType): Boolean = {
2106-
def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match {
2106+
@tailrec def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match {
21072107
case formal1 :: rest1 =>
21082108
formals2 match {
21092109
case formal2 :: rest2 =>
@@ -2116,7 +2116,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
21162116
case nil =>
21172117
formals2.isEmpty
21182118
}
2119-
loop(tp1.paramInfos, tp2.paramInfos)
2119+
tp1.paramPrecises == tp2.paramPrecises && loop(tp1.paramInfos, tp2.paramInfos)
21202120
}
21212121

21222122
// Type equality =:=
@@ -2456,6 +2456,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
24562456
else if (tparams1.hasSameLengthAs(tparams2))
24572457
HKTypeLambda(
24582458
paramNames = HKTypeLambda.syntheticParamNames(tparams1.length),
2459+
paramPrecises = Nil,
24592460
variances =
24602461
if tp1.isDeclaredVarianceLambda && tp2.isDeclaredVarianceLambda then
24612462
tparams1.lazyZip(tparams2).map((p1, p2) => combineVariance(p1.paramVariance, p2.paramVariance))

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ object TypeErasure {
234234

235235
def eraseParamBounds(tp: PolyType): Type =
236236
tp.derivedLambdaType(
237-
tp.paramNames, tp.paramNames map (Function.const(TypeBounds.upper(defn.ObjectType))), tp.resultType)
237+
tp.paramNames, tp.paramPrecises, tp.paramNames map (Function.const(TypeBounds.upper(defn.ObjectType))), tp.resultType)
238238

239239
if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType])
240240
else if (sym.isAbstractType) TypeAlias(WildcardType)
@@ -642,14 +642,14 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
642642
val formals = formals0.mapConserve(paramErasure)
643643
eraseResult(tp.resultType) match {
644644
case rt: MethodType =>
645-
tp.derivedLambdaType(names ++ rt.paramNames, formals ++ rt.paramInfos, rt.resultType)
645+
tp.derivedLambdaType(names ++ rt.paramNames, Nil, formals ++ rt.paramInfos, rt.resultType)
646646
case NoType =>
647647
// Can happen if we smuggle in a Nothing in the qualifier. Normally we prevent that
648648
// in Checking.checkMembersOK, but compiler-generated code can bypass this test.
649649
// See i15377.scala for a test case.
650650
NoType
651651
case rt =>
652-
tp.derivedLambdaType(names, formals, rt)
652+
tp.derivedLambdaType(names, Nil, formals, rt)
653653
}
654654
case tp: PolyType =>
655655
this(tp.resultType)

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

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import collection.mutable
1212
import java.lang.ref.WeakReference
1313
import util.{Stats, SimpleIdentityMap}
1414
import Decorators._
15+
import ast.tpd.Tree
1516

1617
import scala.annotation.internal.sharable
1718

@@ -24,21 +25,23 @@ object TyperState {
2425
.setCommittable(true)
2526

2627
type LevelMap = SimpleIdentityMap[TypeVar, Integer]
28+
type PreciseConversionStack = List[Set[Tree]]
2729

28-
opaque type Snapshot = (Constraint, TypeVars, LevelMap)
30+
opaque type Snapshot = (Constraint, TypeVars, LevelMap, PreciseConversionStack)
2931

3032
extension (ts: TyperState)
3133
def snapshot()(using Context): Snapshot =
32-
(ts.constraint, ts.ownedVars, ts.upLevels)
34+
(ts.constraint, ts.ownedVars, ts.upLevels, ts.myPreciseConvStack)
3335

3436
def resetTo(state: Snapshot)(using Context): Unit =
35-
val (constraint, ownedVars, upLevels) = state
37+
val (constraint, ownedVars, upLevels, myPreciseConvStack) = state
3638
for tv <- ownedVars do
3739
if !ts.ownedVars.contains(tv) then // tv has been instantiated
3840
tv.resetInst(ts)
3941
ts.constraint = constraint
4042
ts.ownedVars = ownedVars
4143
ts.upLevels = upLevels
44+
ts.myPreciseConvStack = myPreciseConvStack
4245
}
4346

4447
class TyperState() {
@@ -93,6 +96,15 @@ class TyperState() {
9396

9497
private var upLevels: LevelMap = _
9598

99+
private var myPreciseConvStack: List[Set[Tree]] = _
100+
def hasPreciseConversion(tree: Tree): Boolean = myPreciseConvStack.head.contains(tree)
101+
def addPreciseConversion(tree: Tree): Unit =
102+
myPreciseConvStack = (myPreciseConvStack.head + tree) :: myPreciseConvStack.tail
103+
def pushPreciseConversionStack(): Unit =
104+
myPreciseConvStack = Set.empty[Tree] :: myPreciseConvStack
105+
def popPreciseConversionStack(): Unit =
106+
myPreciseConvStack = myPreciseConvStack.drop(1)
107+
96108
/** Initializes all fields except reporter, isCommittable, which need to be
97109
* set separately.
98110
*/
@@ -105,6 +117,7 @@ class TyperState() {
105117
this.myOwnedVars = SimpleIdentitySet.empty
106118
this.upLevels = SimpleIdentityMap.empty
107119
this.isCommitted = false
120+
this.myPreciseConvStack = Nil
108121
this
109122

110123
/** A fresh typer state with the same constraint as this one. */
@@ -115,6 +128,7 @@ class TyperState() {
115128
.setReporter(reporter)
116129
.setCommittable(committable)
117130
ts.upLevels = upLevels
131+
ts.myPreciseConvStack = myPreciseConvStack
118132
ts
119133

120134
/** The uninstantiated variables */
@@ -162,6 +176,8 @@ class TyperState() {
162176
assert(!isCommitted, s"$this is already committed")
163177
val targetState = ctx.typerState
164178

179+
targetState.myPreciseConvStack = myPreciseConvStack
180+
165181
val nothingToCommit = (constraint eq targetState.constraint) && !reporter.hasUnreportedMessages
166182
assert(!targetState.isCommitted || nothingToCommit ||
167183
// Committing into an already committed TyperState usually doesn't make

0 commit comments

Comments
 (0)