Skip to content

Commit aada2ca

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

40 files changed

+571
-108
lines changed

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,49 @@ object desugar {
293293
vparam => toDefParam(vparam, keepAnnotations = true, keepDefault = false)
294294
}
295295

296+
def ignoreErrorsAndRun[R](op: => R): R =
297+
val savedState = ctx.typerState.snapshot()
298+
val savedReporter = ctx.reporter
299+
ctx.typerState.setReporter(Reporter.NoReporter)
300+
val ret = op
301+
ctx.typerState.setReporter(savedReporter)
302+
ctx.typerState.resetTo(savedState)
303+
ret
304+
305+
// gets arguments should be considered precise
306+
val paramPrecises: List[Boolean] =
307+
// indication for the type parameters preciseness
308+
val preciseMap : Map[TypeName, Boolean] = {
309+
meth.leadingTypeParams.map(t =>
310+
(t.name, t.mods.annotations.exists {
311+
//TODO: this is a hack. Typing causes problems
312+
case Apply(Select(New(Ident(p)), _), _) => p.toString == "precise"
313+
case _ => false
314+
})
315+
).toMap
316+
}
317+
// mapping type parameters preciseness onto term parameter preciseness
318+
meth.termParamss.view.flatten.map(p => p.tpt).map {
319+
case Ident(n) => preciseMap.getOrElse(n.asTypeName, false)
320+
case _ => false
321+
}.toList
322+
296323
def defaultGetters(paramss: List[ParamClause], n: Int): List[DefDef] = paramss match
297324
case ValDefs(vparam :: vparams) :: paramss1 =>
298325
def defaultGetter: DefDef =
326+
val (rhs, tpt) =
327+
// if the parameter is precise, then we add explicit return type for the
328+
// definition to keep the precise type after precise typing.
329+
if paramPrecises(n) then
330+
val rhsTyped = withMode(Mode.Precise){ctx.typer.typedExpr(vparam.rhs)}
331+
(TypedSplice(rhsTyped), TypeTree(rhsTyped.tpe))
332+
// otherwise, the desugaring is unchanged from the status quo
333+
else (vparam.rhs, TypeTree())
299334
DefDef(
300335
name = DefaultGetterName(meth.name, n),
301336
paramss = getterParamss(n),
302-
tpt = TypeTree(),
303-
rhs = vparam.rhs
337+
tpt = tpt,
338+
rhs = rhs
304339
)
305340
.withMods(Modifiers(
306341
meth.mods.flags & (AccessFlags | Synthetic) | (vparam.mods.flags & Inline),

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

Lines changed: 29 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,35 @@ 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 param.isPrecise | ctx.mode.is(Mode.Precise) => true
560+
case param: TypeParamRef =>
561+
//the type parameter is from an applied type and there it is annotated as precise
562+
constraint.domainLambdas.view.flatMap(_.resType.paramInfoss.flatten).flatMap {
563+
case AppliedType(tycon, args) =>
564+
val preciseArgIdx = tycon.typeParamSymbols.indexWhere(_.paramPrecise)
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+
case _ => false
571+
572+
def isPrecise(tp: Type): Boolean =
573+
isPreciseRecur(tp, constraint.uninstVars.view.reverse.map(_.origin).toList)
574+
548575
val wideInst =
549-
if isSingleton(bound) then inst
576+
if isSingleton(bound) || isPrecise(bound) then inst
550577
else dropTransparentTraits(widenIrreducible(widenOr(widenSingle(inst))), bound)
551578
wideInst match
552579
case wideInst: TypeRef if wideInst.symbol.is(Module) =>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class Definitions {
164164
useCompleter: Boolean = false) = {
165165
val tparamNames = PolyType.syntheticParamNames(typeParamCount)
166166
val tparamInfos = tparamNames map (_ => bounds)
167-
def ptype = PolyType(tparamNames)(_ => tparamInfos, resultTypeFn)
167+
def ptype = PolyType(tparamNames, Nil)(_ => tparamInfos, resultTypeFn)
168168
val info =
169169
if (useCompleter)
170170
new LazyType {
@@ -695,7 +695,7 @@ class Definitions {
695695
case meth: MethodType =>
696696
info.derivedLambdaType(
697697
resType = meth.derivedLambdaType(
698-
paramNames = Nil, paramInfos = Nil))
698+
paramNames = Nil, paramPrecises = Nil, paramInfos = Nil))
699699
}
700700
}
701701
val argConstr = constr.copy().entered
@@ -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/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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -523,7 +523,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
523523
val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos: @unchecked
524524
paramInfos = TypeBounds(lo, LazyRef.of(hi)) :: pinfos1
525525
}
526-
ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType))
526+
ensureFresh(tl.newLikeThis(tl.paramNames, tl.paramPrecises, paramInfos, tl.resultType))
527527
}
528528
else tl
529529

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
@@ -1483,6 +1483,11 @@ object SymDenotations {
14831483
else if is(Contravariant) then Contravariant
14841484
else EmptyFlags
14851485

1486+
/** The precise enforcement indicator of this type parameter or type member
1487+
*/
1488+
final def precise(using Context): Boolean =
1489+
hasAnnotation(defn.PreciseAnnot)
1490+
14861491
/** The flags to be used for a type parameter owned by this symbol.
14871492
* Overridden by ClassDenotation.
14881493
*/

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10191019
variancesConform(remainingTparams, tparams) && {
10201020
val adaptedTycon =
10211021
if d > 0 then
1022-
HKTypeLambda(remainingTparams.map(_.paramName))(
1022+
HKTypeLambda(remainingTparams.map(_.paramName), remainingTparams.map(_.paramPrecise))(
10231023
tl => remainingTparams.map(remainingTparam =>
10241024
tl.integrate(remainingTparams, remainingTparam.paramInfo).bounds),
10251025
tl => otherTycon.appliedTo(
@@ -2365,6 +2365,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
23652365
else if (tparams1.hasSameLengthAs(tparams2))
23662366
HKTypeLambda(
23672367
paramNames = HKTypeLambda.syntheticParamNames(tparams1.length),
2368+
paramPrecises = Nil,
23682369
variances =
23692370
if tp1.isDeclaredVarianceLambda && tp2.isDeclaredVarianceLambda then
23702371
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: 9 additions & 0 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
1414
import Decorators._
15+
import ast.tpd.Tree
1516

1617
import scala.annotation.internal.sharable
1718

@@ -89,6 +90,11 @@ class TyperState() {
8990
def ownedVars: TypeVars = myOwnedVars
9091
def ownedVars_=(vs: TypeVars): Unit = myOwnedVars = vs
9192

93+
private var myPreciseConversions: Set[Tree] = Set()
94+
def hasPreciseConversion(tree: Tree): Boolean = myPreciseConversions.contains(tree)
95+
def addPreciseConversion(tree: Tree): Unit = myPreciseConversions = myPreciseConversions + tree
96+
def getPreciseConversions: Set[Tree] = myPreciseConversions
97+
9298
/** Initializes all fields except reporter, isCommittable, which need to be
9399
* set separately.
94100
*/
@@ -100,6 +106,7 @@ class TyperState() {
100106
this.previousConstraint = constraint
101107
this.myOwnedVars = SimpleIdentitySet.empty
102108
this.isCommitted = false
109+
this.myPreciseConversions = Set()
103110
this
104111

105112
/** A fresh typer state with the same constraint as this one. */
@@ -143,6 +150,8 @@ class TyperState() {
143150
assert(!isCommitted, s"$this is already committed")
144151
val targetState = ctx.typerState
145152

153+
targetState.myPreciseConversions = targetState.myPreciseConversions ++ myPreciseConversions
154+
146155
val nothingToCommit = (constraint eq targetState.constraint) && !reporter.hasUnreportedMessages
147156
assert(!targetState.isCommitted || nothingToCommit ||
148157
// Committing into an already committed TyperState usually doesn't make

0 commit comments

Comments
 (0)