Skip to content

Commit 3df158a

Browse files
mbovelsoronpo
authored andcommitted
Precise annotation SIP
1 parent 104e8df commit 3df158a

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

+1098
-131
lines changed

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

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

259+
//TODO: this is a hack. Typing causes problems (e.g. scaladoc)
260+
def isPreciseAnnot(tree: untpd.Tree)(using Context): Boolean =
261+
tree match
262+
case Apply(Select(New(Ident(precise)), _), _) => precise.toString == "precise"
263+
case _ => false
264+
259265
def addDefaultGetters(meth: DefDef)(using Context): Tree =
260266

261267
/** The longest prefix of parameter lists in paramss whose total number of
@@ -293,14 +299,42 @@ object desugar {
293299
vparam => toDefParam(vparam, keepAnnotations = true, keepDefault = false)
294300
}
295301

302+
def ignoreErrorsAndRun[R](op: => R): R =
303+
val savedState = ctx.typerState.snapshot()
304+
val savedReporter = ctx.reporter
305+
ctx.typerState.setReporter(Reporter.NoReporter)
306+
val ret = op
307+
ctx.typerState.setReporter(savedReporter)
308+
ctx.typerState.resetTo(savedState)
309+
ret
310+
311+
// gets arguments should be considered precise
312+
val paramPrecises: List[Boolean] =
313+
// indication for the type parameters preciseness
314+
val preciseMap : Map[TypeName, Boolean] =
315+
meth.leadingTypeParams.map(t => (t.name, t.mods.annotations.exists(isPreciseAnnot))).toMap
316+
// mapping type parameters preciseness onto term parameter preciseness
317+
meth.termParamss.view.flatten.map(p => p.tpt).map {
318+
case Ident(n) => preciseMap.getOrElse(n.asTypeName, false)
319+
case _ => false
320+
}.toList
321+
296322
def defaultGetters(paramss: List[ParamClause], n: Int): List[DefDef] = paramss match
297323
case ValDefs(vparam :: vparams) :: paramss1 =>
298324
def defaultGetter: DefDef =
325+
val (rhs, tpt) =
326+
// if the parameter is precise, then we add explicit return type for the
327+
// definition to keep the precise type after precise typing.
328+
if paramPrecises(n) then
329+
val rhsTyped = withMode(Mode.Precise){ctx.typer.typedExpr(vparam.rhs)}
330+
(TypedSplice(rhsTyped), TypeTree(rhsTyped.tpe))
331+
// otherwise, the desugaring is unchanged from the status quo
332+
else (vparam.rhs, TypeTree())
299333
DefDef(
300334
name = DefaultGetterName(meth.name, n),
301335
paramss = getterParamss(n),
302-
tpt = TypeTree(),
303-
rhs = vparam.rhs
336+
tpt = tpt,
337+
rhs = rhs
304338
)
305339
.withMods(Modifiers(
306340
meth.mods.flags & (AccessFlags | Synthetic) | (vparam.mods.flags & Inline),
@@ -381,9 +415,10 @@ object desugar {
381415

382416
@sharable private val synthetic = Modifiers(Synthetic)
383417

384-
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean): TypeDef = {
418+
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean)(using Context): TypeDef = {
385419
var mods = tparam.rawMods
386-
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
420+
val onlyPreciseAnnot = mods.annotations.filter(isPreciseAnnot)
421+
if (!keepAnnotations) mods = mods.withAnnotations(onlyPreciseAnnot)
387422
tparam.withMods(mods & EmptyFlags | Param)
388423
}
389424
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: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import typer.ProtoTypes.{newTypeVar, representedParamRef}
1313
import UnificationDirection.*
1414
import NameKinds.AvoidNameKind
1515
import util.SimpleIdentitySet
16-
16+
import annotation.tailrec
1717
/** Methods for adding constraints and solving them.
1818
*
1919
* What goes into a Constraint as opposed to a ConstrainHandler?
@@ -654,8 +654,37 @@ trait ConstraintHandling {
654654
case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi)
655655
case _ => isSubTypeWhenFrozen(tp, defn.SingletonType)
656656

657+
def derivesPreciseAnnot(tp: Type): Boolean =
658+
tp.derivesAnnotWith(_.matches(defn.PreciseAnnot))
659+
660+
def dependsOnParam(tp: Type, param: TypeParamRef) : Boolean =
661+
tp match
662+
case v: TypeVar => v.origin == param
663+
case AppliedType(_, args) => args.exists(dependsOnParam(_, param))
664+
case _ => false
665+
666+
def isPreciseRecur(tp: Type, uninstParams : List[TypeParamRef]): Boolean = tp match
667+
//the type parameter is annotated as precise or the mode is precise
668+
case param: TypeParamRef if param.isPrecise | ctx.mode.is(Mode.Precise) => true
669+
//the type parameter is upper-bounded by a precise
670+
case param: TypeParamRef if constraint.upper(param).exists(_.isPrecise) => true
671+
case param: TypeParamRef =>
672+
//the type parameter is from an applied type and there it is annotated as precise
673+
constraint.domainLambdas.view.flatMap(_.resType.paramInfoss.flatten).flatMap {
674+
case AppliedType(tycon, args) =>
675+
val preciseArgIdx = tycon.typeParamSymbols.indexWhere(_.paramPrecise)
676+
if (preciseArgIdx >= 0) Some(args(preciseArgIdx) == param)
677+
else None
678+
case p : TypeParamRef => if (p == param) Some(false) else None
679+
case _ => None
680+
}.headOption.getOrElse(false)
681+
case _ => false
682+
683+
def isPrecise(tp: Type): Boolean =
684+
isPreciseRecur(tp, constraint.uninstVars.view.reverse.map(_.origin).toList)
685+
657686
val wideInst =
658-
if isSingleton(bound) then inst
687+
if isSingleton(bound) || isPrecise(bound) then inst
659688
else dropTransparentTraits(widenIrreducible(widenOr(widenSingle(inst))), bound)
660689
wideInst match
661690
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
@@ -529,7 +529,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
529529
val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos: @unchecked
530530
paramInfos = TypeBounds(lo, LazyRef.of(hi)) :: pinfos1
531531
}
532-
ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType))
532+
ensureFresh(tl.newLikeThis(tl.paramNames, tl.paramPrecises, paramInfos, tl.resultType))
533533
}
534534
else tl
535535

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.
@@ -1088,7 +1088,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10881088
variancesConform(remainingTparams, tparams) && {
10891089
val adaptedTycon =
10901090
if d > 0 then
1091-
HKTypeLambda(remainingTparams.map(_.paramName))(
1091+
HKTypeLambda(remainingTparams.map(_.paramName), remainingTparams.map(_.paramPrecise))(
10921092
tl => remainingTparams.map(remainingTparam =>
10931093
tl.integrate(remainingTparams, remainingTparam.paramInfo).bounds),
10941094
tl => otherTycon.appliedTo(
@@ -2094,7 +2094,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
20942094
* to override `tp2` ? This is the case if they're pairwise >:>.
20952095
*/
20962096
def matchingPolyParams(tp1: PolyType, tp2: PolyType): Boolean = {
2097-
def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match {
2097+
@tailrec def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match {
20982098
case formal1 :: rest1 =>
20992099
formals2 match {
21002100
case formal2 :: rest2 =>
@@ -2107,7 +2107,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
21072107
case nil =>
21082108
formals2.isEmpty
21092109
}
2110-
loop(tp1.paramInfos, tp2.paramInfos)
2110+
tp1.paramPrecises == tp2.paramPrecises && loop(tp1.paramInfos, tp2.paramInfos)
21112111
}
21122112

21132113
// Type equality =:=
@@ -2447,6 +2447,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
24472447
else if (tparams1.hasSameLengthAs(tparams2))
24482448
HKTypeLambda(
24492449
paramNames = HKTypeLambda.syntheticParamNames(tparams1.length),
2450+
paramPrecises = Nil,
24502451
variances =
24512452
if tp1.isDeclaredVarianceLambda && tp2.isDeclaredVarianceLambda then
24522453
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

0 commit comments

Comments
 (0)