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 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/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 1dfa04822766..4bd401a5af4d 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -643,11 +643,12 @@ 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 = - 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) => diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 83d945352321..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 @@ -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/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/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") } 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/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/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index ef6b12f3c6d4..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. @@ -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( @@ -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 =:= @@ -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/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 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/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index bc00897d7783..b3ca0cb0a695 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 @@ -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) @@ -804,7 +807,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 @@ -1312,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 => @@ -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 = @@ -3560,6 +3563,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 +3609,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 +3876,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 +3907,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 +3928,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 +3969,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 +4015,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 +4050,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 +4072,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 +4094,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 +4118,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 +4145,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 +4156,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 +4205,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 +4228,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 +4260,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 @@ -4331,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 @@ -4515,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. @@ -4638,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 @@ -5207,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 = _ @@ -5306,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 @@ -5315,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 @@ -5333,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. @@ -5554,7 +5609,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 +6066,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 = @@ -6040,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/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/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) 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/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/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/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..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 @@ -1478,7 +1513,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 +1623,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 +1712,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 +2333,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/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/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ad8d0e50d348..013dca38e4ed 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 @@ -1854,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)) @@ -1876,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`). 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/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 71b500dc04a9..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 @@ -733,7 +749,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) @@ -767,7 +783,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)) @@ -804,7 +820,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 @@ -825,7 +841,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 +865,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 +876,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 +887,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 +897,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/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") 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/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b05ba9d1ca43..60299f03ffe5 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 = { @@ -1061,7 +1097,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) @@ -1197,7 +1236,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 +1925,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 } @@ -1913,7 +1952,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 +2725,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 +3086,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 = @@ -3440,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) @@ -3975,6 +4058,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 => 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/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 { + +} 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/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-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 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-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 } 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 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" )