diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 0a2c0c850e5d..b71af156262f 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -588,9 +588,13 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case New(_) | Closure(_, _, _) => Pure case TypeApply(fn, _) => + val sym = fn.symbol if tree.tpe.isInstanceOf[MethodOrPoly] then exprPurity(fn) - else if fn.symbol == defn.QuotedTypeModule_of || fn.symbol == defn.Predef_classOf then Pure - else if fn.symbol == defn.Compiletime_erasedValue && tree.tpe.dealias.isInstanceOf[ConstantType] then Pure + else if sym == defn.QuotedTypeModule_of + || sym == defn.Predef_classOf + || sym == defn.Compiletime_erasedValue && tree.tpe.dealias.isInstanceOf[ConstantType] + || defn.capsErasedValueMethods.contains(sym) + then Pure else Impure case Apply(fn, args) => val factorPurity = minOf(exprPurity(fn), args.map(exprPurity)) @@ -634,6 +638,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => def isPureBinding(tree: Tree)(using Context): Boolean = statPurity(tree) >= Pure + def isPureSyntheticCaseApply(sym: Symbol)(using Context): Boolean = + sym.isAllOf(SyntheticMethod) + && sym.name == nme.apply + && sym.owner.is(Module) + && { + val cls = sym.owner.companionClass + cls.is(Case) && cls.isNoInitsRealClass + } + /** Is the application `tree` with function part `fn` known to be pure? * Function value and arguments can still be impure. */ @@ -645,6 +658,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => tree.tpe.isInstanceOf[ConstantType] && tree.symbol != NoSymbol && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure. || fn.symbol.isStableMember && fn.symbol.isConstructor // constructors of no-inits classes are stable + || isPureSyntheticCaseApply(fn.symbol) /** The purity level of this reference. * @return @@ -653,8 +667,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * or its type is a constant type * IdempotentPath if reference is lazy and stable * Impure otherwise - * @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable - * flags set. */ def refPurity(tree: Tree)(using Context): PurityLevel = { val sym = tree.symbol diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 991309293c0c..92c20afe7a73 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -301,7 +301,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { assert(vparams.hasSameLengthAs(tp.paramNames) && vparams.head.isTerm) (vparams.asInstanceOf[List[TermSymbol]], remaining1) case nil => - (tp.paramNames.lazyZip(tp.paramInfos).lazyZip(tp.erasedParams).map(valueParam), Nil) + (tp.paramNames.lazyZip(tp.paramInfos).lazyZip(tp.paramErasureStatuses).map(valueParam), Nil) val (rtp, paramss) = recur(tp.instantiate(vparams.map(_.termRef)), remaining1) (rtp, vparams :: paramss) case _ => diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 2f5c59c11071..d21e9c7d5064 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -495,15 +495,13 @@ extension (sym: Symbol) /** Does this symbol allow results carrying the universal capability? * Currently this is true only for function type applies (since their - * results are unboxed) and `erasedValue` since this function is magic in - * that is allows to conjure global capabilies from nothing (aside: can we find a - * more controlled way to achieve this?). + * results are unboxed) and `caps.{$internal,unsafe}.erasedValue` since + * these function are magic in that they allow to conjure global capabilies from nothing. * But it could be generalized to other functions that so that they can take capability * classes as arguments. */ def allowsRootCapture(using Context): Boolean = - sym == defn.Compiletime_erasedValue - || defn.isFunctionClass(sym.maybeOwner) + defn.capsErasedValueMethods.contains(sym) || defn.isFunctionClass(sym.maybeOwner) /** When applying `sym`, would the result type be unboxed? * This is the case if the result type contains a top-level reference to an enclosing diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index dccbd0a005d7..e8cc0eb69528 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -576,7 +576,7 @@ class CheckCaptures extends Recheck, SymTransformer: * @param args the type arguments */ def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit = - def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue + def isExempt = sym.isTypeTestOrCast || defn.capsErasedValueMethods.contains(sym) if !isExempt then val paramNames = atPhase(thisPhase.prev): fn.tpe.widenDealias match diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 81b03d765676..f2e426612eeb 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -48,8 +48,6 @@ object CheckRealizable { def boundsRealizability(tp: Type)(using Context): Realizability = new CheckRealizable().boundsRealizability(tp) - - private val LateInitializedFlags = Lazy | Erased } /** Compute realizability status. @@ -72,7 +70,7 @@ class CheckRealizable(using Context) { /** Is symbol's definitition a lazy or erased val? * (note we exclude modules here, because their realizability is ensured separately) */ - private def isLateInitialized(sym: Symbol) = sym.isOneOf(LateInitializedFlags, butNot = Module) + private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module) /** The realizability status of given type `tp`*/ def realizability(tp: Type): Realizability = tp.dealias match { @@ -184,7 +182,7 @@ class CheckRealizable(using Context) { private def memberRealizability(tp: Type) = { def checkField(sofar: Realizability, fld: SingleDenotation): Realizability = sofar andAlso { - if (checkedFields.contains(fld.symbol) || fld.symbol.isOneOf(Private | Mutable | LateInitializedFlags)) + if (checkedFields.contains(fld.symbol) || fld.symbol.isOneOf(Private | Mutable | Lazy)) // if field is private it cannot be part of a visible path // if field is mutable it cannot be part of a path // if field is lazy or erased it does not need to be initialized when the owning object is diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 381caa775dbd..531e8a5aa21f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -266,6 +266,7 @@ class Definitions { @tu lazy val CompiletimeOpsDoubleModuleClass: Symbol = requiredModule("scala.compiletime.ops.double").moduleClass @tu lazy val CompiletimeOpsStringModuleClass: Symbol = requiredModule("scala.compiletime.ops.string").moduleClass @tu lazy val CompiletimeOpsBooleanModuleClass: Symbol = requiredModule("scala.compiletime.ops.boolean").moduleClass + @tu lazy val ErasedClass: ClassSymbol = requiredClass("scala.compiletime.Erased") /** Note: We cannot have same named methods defined in Object and Any (and AnyVal, for that matter) * because after erasure the Any and AnyVal references get remapped to the Object methods @@ -543,7 +544,7 @@ class Definitions { // needed as a synthetic class because Scala 2.x refers to it in classfiles // but does not define it as an explicit class. val cls = enterCompleteClassSymbol( - ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final | Erased, + ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final, List(AnyType)) enterTypeField(cls, tpnme.Self, Deferred, cls.info.decls.openForMutations) cls @@ -1004,16 +1005,18 @@ class Definitions { @tu lazy val Caps_Capability: ClassSymbol = requiredClass("scala.caps.Capability") @tu lazy val Caps_CapSet: ClassSymbol = requiredClass("scala.caps.CapSet") @tu lazy val CapsInternalModule: Symbol = requiredModule("scala.caps.internal") + @tu lazy val Caps_erasedValue: Symbol = CapsInternalModule.requiredMethod("erasedValue") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") @tu lazy val Caps_unsafeAssumeSeparate: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumeSeparate") + @tu lazy val Caps_unsafeErasedValue: Symbol = CapsUnsafeModule.requiredMethod("unsafeErasedValue") @tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains") @tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains") @tu lazy val Caps_containsImpl: TermSymbol = Caps_ContainsModule.requiredMethod("containsImpl") @tu lazy val Caps_Mutable: ClassSymbol = requiredClass("scala.caps.Mutable") @tu lazy val Caps_SharedCapability: ClassSymbol = requiredClass("scala.caps.SharedCapability") - @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") + @tu lazy val PureClass: ClassSymbol = requiredClass("scala.Pure") // Annotation base classes @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") @@ -1558,6 +1561,11 @@ class Definitions { @tu lazy val pureSimpleClasses = Set(StringClass, NothingClass, NullClass) ++ ScalaValueClasses() + @tu lazy val capsErasedValueMethods = + Set(Caps_erasedValue, Caps_unsafeErasedValue) + @tu lazy val erasedValueMethods = + capsErasedValueMethods + Compiletime_erasedValue + @tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0).asInstanceOf[Array[TypeRef]] val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass)) def AbstractFunctionClass(n: Int)(using Context): Symbol = AbstractFunctionClassPerRun()(using ctx)(n) @@ -2001,7 +2009,9 @@ class Definitions { /** A allowlist of Scala-2 classes that are known to be pure */ def isAssuredNoInits(sym: Symbol): Boolean = - (sym `eq` SomeClass) || isTupleClass(sym) + (sym `eq` SomeClass) + || isTupleClass(sym) + || sym.is(Module) && isAssuredNoInits(sym.companionClass) /** If `cls` is Tuple1..Tuple22, add the corresponding *: type as last parent to `parents` */ def adjustForTuple(cls: ClassSymbol, tparams: List[TypeSymbol], parents: List[Type]): List[Type] = { diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index c040d3e206b9..b69f9142ee46 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -540,7 +540,7 @@ object Flags { val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | Enum /** Flags retained in term export forwarders */ - val RetainedExportTermFlags = Infix | Given | Implicit | Inline | Transparent | Erased | HasDefaultParams | NoDefaultParams | ExtensionMethod + val RetainedExportTermFlags = Infix | Given | Implicit | Inline | Transparent | HasDefaultParams | NoDefaultParams | ExtensionMethod /** Flags retained in parameters of term export forwarders */ val RetainedExportTermParamFlags = Given | Implicit | Erased | HasDefault | Inline @@ -569,7 +569,6 @@ object Flags { val EnumCase: FlagSet = Case | Enum val CovariantLocal: FlagSet = Covariant | Local // A covariant type parameter val ContravariantLocal: FlagSet = Contravariant | Local // A contravariant type parameter - val EffectivelyErased = PhantomSymbol | Erased val ConstructorProxyModule: FlagSet = PhantomSymbol | Module val CaptureParam: FlagSet = PhantomSymbol | StableRealizable | Synthetic val DefaultParameter: FlagSet = HasDefault | Param // A Scala 2x default parameter diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index bd43578e3d53..2cac0a6d5b2f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1051,8 +1051,13 @@ object SymDenotations { && owner.ne(defn.StringContextClass) /** An erased value or an erased inline method or field */ + def isErased(using Context): Boolean = + is(Erased) || defn.erasedValueMethods.contains(symbol) + + /** An erased value, a phantom symbol or an erased inline method or field */ def isEffectivelyErased(using Context): Boolean = - isOneOf(EffectivelyErased) + isErased + || is(PhantomSymbol) || is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot) /** Is this a member that will become public in the generated binary */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index eb03a2b1c05d..5753d311baa9 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2387,7 +2387,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling formals2.isEmpty } // If methods have erased parameters, then the erased parameters must match - val erasedValid = (!tp1.hasErasedParams && !tp2.hasErasedParams) || (tp1.erasedParams == tp2.erasedParams) + val erasedValid = (!tp1.hasErasedParams && !tp2.hasErasedParams) || (tp1.paramErasureStatuses == tp2.paramErasureStatuses) erasedValid && loop(tp1.paramInfos, tp2.paramInfos) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 83f087239477..2e6fa7d94d43 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -697,7 +697,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst val (names, formals0) = if tp.hasErasedParams then tp.paramNames .zip(tp.paramInfos) - .zip(tp.erasedParams) + .zip(tp.paramErasureStatuses) .collect{ case (param, isErased) if !isErased => param } .unzip else (tp.paramNames, tp.paramInfos) diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 82c027744c38..d2a8a499c90a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -23,13 +23,6 @@ class TypeUtils: def isPrimitiveValueType(using Context): Boolean = self.classSymbol.isPrimitiveValueClass - def isErasedClass(using Context): Boolean = - val cls = self.underlyingClassRef(refinementOK = true).typeSymbol - cls.is(Flags.Erased) - && (cls != defn.SingletonClass || Feature.enabled(Feature.modularity)) - // Singleton counts as an erased class only under x.modularity - - /** Is this type a checked exception? This is the case if the type * derives from Exception but not from RuntimeException. According to * that definition Throwable is unchecked. That makes sense since you should diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 61b3b958fca3..444da66253ca 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -275,7 +275,7 @@ object Types extends TypeUtils { tp.isBottomType && (tp.hasClassSymbol(defn.NothingClass) || cls != defn.NothingClass && !cls.isValueClass) - def loop(tp: Type): Boolean = tp match { + def loop(tp: Type): Boolean = try tp match case tp: TypeRef => val sym = tp.symbol if (sym.isClass) sym.derivesFrom(cls) else loop(tp.superType) @@ -301,7 +301,7 @@ object Types extends TypeUtils { cls == defn.ObjectClass case _ => false - } + catch case ex: Throwable => handleRecursive(i"derivesFrom $cls:", show, ex) loop(this) } @@ -3931,7 +3931,7 @@ object Types extends TypeUtils { case tp: MethodType => val params = if (hasErasedParams) tp.paramInfos - .zip(tp.erasedParams) + .zip(tp.paramErasureStatuses) .collect { case (param, isErased) if !isErased => param } else tp.paramInfos resultSignature.prependTermParams(params, sourceLanguage) @@ -4163,7 +4163,7 @@ object Types extends TypeUtils { final override def isContextualMethod: Boolean = companion.eq(ContextualMethodType) - def erasedParams(using Context): List[Boolean] = + def paramErasureStatuses(using Context): List[Boolean] = paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot)) def nonErasedParamCount(using Context): Int = diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 3edb323e6b3b..a1058b18e32c 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -172,6 +172,19 @@ class InlineReducer(inliner: Inliner)(using Context): val isImplicit = scrutinee.isEmpty + val unusable: util.EqHashSet[Symbol] = util.EqHashSet() + + /** Adjust internaly generated value definitions; + * - If the RHS refers to an erased symbol, mark the val as erased + * - If the RHS refers to an erased symbol, mark the val as unsuable + */ + def adjustErased(sym: TermSymbol, rhs: Tree): Unit = + rhs.foreachSubTree: + case id: Ident if id.symbol.isErased => + sym.setFlag(Erased) + if unusable.contains(id.symbol) then unusable += sym + case _ => + /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add * bindings for variables bound in this pattern to `caseBindingMap`. */ @@ -184,10 +197,11 @@ class InlineReducer(inliner: Inliner)(using Context): /** Create a binding of a pattern bound variable with matching part of * scrutinee as RHS and type that corresponds to RHS. */ - def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { - val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, flags = sym.flags &~ Case).asTerm + def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = + val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, + flags = sym.flags &~ Case).asTerm + adjustErased(copied, rhs) caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) - } def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = { val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType @@ -306,6 +320,7 @@ class InlineReducer(inliner: Inliner)(using Context): case (Nil, Nil) => true case (pat :: pats1, selector :: selectors1) => val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm + adjustErased(elem, selector) val rhs = constToLiteral(selector) elem.defTree = rhs caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span))) @@ -341,6 +356,19 @@ class InlineReducer(inliner: Inliner)(using Context): val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) + // If scrutinee has embedded references to `compiletime.erasedValue` or to + // other erased values, mark scrutineeSym as Erased. In addition, if scrutinee + // is not a pure expression, mark scrutineeSym as unusable. The reason is that + // scrutinee would then fail the tests in erasure that demand that the RHS of + // an erased val is a pure expression. At the end of the inline match reduction + // we throw out all unusable vals and check that the remaining code does not refer + // to unusable symbols. + // Note that compiletime.erasedValue is treated as erased but not pure, so scrutinees + // containing references to it becomes unusable. + if scrutinee.existsSubTree(_.symbol.isErased) then + scrutineeSym.setFlag(Erased) + if !tpd.isPureExpr(scrutinee) then unusable += scrutineeSym + def reduceCase(cdef: CaseDef): MatchReduxWithGuard = { val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]() @@ -382,7 +410,25 @@ class InlineReducer(inliner: Inliner)(using Context): case _ => None } - recur(cases) + for (bindings, expr) <- recur(cases) yield + // drop unusable vals and check that no referenes to unusable symbols remain + val cleanupUnusable = new TreeMap: + override def transform(tree: Tree)(using Context): Tree = + tree match + case tree: ValDef if unusable.contains(tree.symbol) => EmptyTree + case id: Ident if unusable.contains(id.symbol) => + report.error( + em"""${id.symbol} is unusable in ${ctx.owner} because it refers to an erased expression + |in the selector of an inline match that reduces to + | + |${Block(bindings, expr)}""", + tree.srcPos) + tree + case _ => super.transform(tree) + + val bindings1 = bindings.mapConserve(cleanupUnusable.transform).collect: + case mdef: MemberDef => mdef + (bindings1, cleanupUnusable.transform(expr)) } end InlineReducer diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 047ab80e6b0f..4b5ead7f19ca 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -58,12 +58,12 @@ object Inliner: case Ident(_) => isPureRef(tree) || tree.symbol.isAllOf(InlineParam) case Select(qual, _) => - if (tree.symbol.is(Erased)) true + if tree.symbol.isErased then true else isPureRef(tree) && apply(qual) case New(_) | Closure(_, _, _) => true case TypeApply(fn, _) => - if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) + if fn.symbol.isErased || fn.symbol == defn.QuotedTypeModule_of then true else apply(fn) case Apply(fn, args) => val isCaseClassApply = { val cls = tree.tpe.classSymbol diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e4314b27a32c..f7382e5fa74d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -214,9 +214,8 @@ object Parsers { def isIdent(name: Name) = in.isIdent(name) def isPureArrow(name: Name): Boolean = isIdent(name) && Feature.pureFunsEnabled def isPureArrow: Boolean = isPureArrow(nme.PUREARROW) || isPureArrow(nme.PURECTXARROW) - def isErased = isIdent(nme.erased) && in.erasedEnabled - // Are we seeing an `erased` soft keyword that will not be an identifier? - def isErasedKw = isErased && in.isSoftModifierInParamModifierPosition + def isErased = + isIdent(nme.erased) && in.erasedEnabled && in.isSoftModifierInParamModifierPosition def isSimpleLiteral = simpleLiteralTokens.contains(in.token) || isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) @@ -1725,8 +1724,8 @@ object Parsers { else val paramStart = in.offset def addErased() = - erasedArgs.addOne(isErasedKw) - if isErasedKw then in.skipToken() + erasedArgs.addOne(isErased) + if isErased then in.skipToken() addErased() val args = in.currentRegion.withCommasExpected: @@ -2625,7 +2624,7 @@ object Parsers { */ def binding(mods: Modifiers): Tree = atSpan(in.offset) { - val mods1 = if isErasedKw then addModifier(mods) else mods + val mods1 = if isErased then addModifier(mods) else mods makeParameter(bindingName(), typedOpt(), mods1) } @@ -2828,7 +2827,7 @@ object Parsers { else in.currentRegion.withCommasExpected { var isFormalParams = false def exprOrBinding() = - if isErasedKw then isFormalParams = true + if isErased then isFormalParams = true if isFormalParams then binding(Modifiers()) else val t = maybeNamed(exprInParens)() @@ -3579,7 +3578,7 @@ object Parsers { def param(): ValDef = { val start = in.offset var mods = impliedMods.withAnnotations(annotations()) - if isErasedKw then + if isErased then mods = addModifier(mods) if paramOwner.isClass then mods = addFlag(modifiers(start = mods), ParamAccessor) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 9987eaaa81b9..332ceb028a27 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -1236,8 +1236,6 @@ object Scanners { def isSoftModifierInParamModifierPosition: Boolean = isSoftModifier && !lookahead.isColon - def isErased: Boolean = isIdent(nme.erased) && erasedEnabled - def canStartStatTokens = if migrateTo3 then canStartStatTokens2 else canStartStatTokens3 diff --git a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala index dbc2694dc891..816bac14ddd2 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala @@ -127,8 +127,8 @@ class Interpreter(pos: SrcPos, classLoader0: ClassLoader)(using Context): case fnType: MethodType => val argTypes = fnType.paramInfos assert(argss.head.size == argTypes.size) - val nonErasedArgs = argss.head.lazyZip(fnType.erasedParams).collect { case (arg, false) => arg }.toList - val nonErasedArgTypes = fnType.paramInfos.lazyZip(fnType.erasedParams).collect { case (arg, false) => arg }.toList + val nonErasedArgs = argss.head.lazyZip(fnType.paramErasureStatuses).collect { case (arg, false) => arg }.toList + val nonErasedArgTypes = fnType.paramInfos.lazyZip(fnType.paramErasureStatuses).collect { case (arg, false) => arg }.toList assert(nonErasedArgs.size == nonErasedArgTypes.size) interpretArgsGroup(nonErasedArgs, nonErasedArgTypes) ::: interpretArgs(argss.tail, fnType.resType) case fnType: AppliedType if defn.isContextFunctionType(fnType) => diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 96942f913934..99e1435d0e65 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -229,6 +229,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case PointlessAppliedConstructorTypeID // errorNumber: 213 case IllegalContextBoundsID // errorNumber: 214 case NamedPatternNotApplicableID // errorNumber: 215 + case ErasedNotPureID // errornumber 216 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 4974884f8286..4efd5e703834 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3587,3 +3587,35 @@ final class NamedPatternNotApplicable(selectorType: Type)(using Context) extends i"Named patterns cannot be used with $selectorType, because it is not a named tuple or case class" override protected def explain(using Context): String = "" + +final class ErasedNotPure(tree: tpd.Tree, isArgument: Boolean, isImplicit: Boolean)(using Context) extends TypeMsg(ErasedNotPureID): + def what = + if isArgument then s"${if isImplicit then "implicit " else ""}argument to an erased parameter" + else "right-hand-side of an erased value" + override protected def msg(using Context): String = + i"$what fails to be a pure expression" + + override protected def explain(using Context): String = + def alternatives = + if tree.symbol == defn.Compiletime_erasedValue then + i"""An accepted (but unsafe) alternative for this expression uses function + | + | caps.unsafe.unsafeErasedValue + | + |instead.""" + else + """A pure expression is an expression that is clearly side-effect free and terminating. + |Some examples of pure expressions are: + | - literals, + | - references to values, + | - side-effect-free instance creations, + | - applications of inline functions to pure arguments.""" + + i"""The $what must be a pure expression, but I found: + | + | $tree + | + |This expression is not classified to be pure. + |$alternatives""" +end ErasedNotPure + diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 3503c707aed9..c743e757b8b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -105,12 +105,6 @@ class Erasure extends Phase with DenotTransformer { if oldSymbol.isRetainedInlineMethod then newFlags = newFlags &~ Flags.Inline newAnnotations = newAnnotations.filterConserve(!_.isInstanceOf[BodyAnnotation]) - oldSymbol match - case cls: ClassSymbol if cls.is(Flags.Erased) => - newFlags = newFlags | Flags.Trait | Flags.JavaInterface - newAnnotations = Nil - newInfo = erasedClassInfo(cls) - case _ => // TODO: define derivedSymDenotation? if ref.is(Flags.PackageClass) || !ref.isClass // non-package classes are always copied since their base types change @@ -550,8 +544,11 @@ object Erasure { case _ => tree.symbol.isEffectivelyErased } - /** Check that Java statics and packages can only be used in selections. - */ + /** Check that + * - erased values are not referred to from normal code + * - inline method applications were inlined + * - Java statics and packages can only be used in selections. + */ private def checkNotErased(tree: Tree)(using Context): tree.type = if !ctx.mode.is(Mode.Type) then if isErased(tree) then @@ -579,24 +576,17 @@ object Erasure { |it should have been processed and eliminated during expansion of an enclosing macro or term erasure.""" report.error(message, tree.srcPos) case _ => // OK - - checkNotErasedClass(tree) + tree end checkNotErased - private def checkNotErasedClass(tp: Type, tree: untpd.Tree)(using Context): Unit = tp match - case JavaArrayType(et) => - checkNotErasedClass(et, tree) - case _ => - if tp.isErasedClass then - val (kind, tree1) = tree match - case tree: untpd.ValOrDefDef => ("definition", tree.tpt) - case tree: untpd.DefTree => ("definition", tree) - case _ => ("expression", tree) - report.error(em"illegal reference to erased ${tp.typeSymbol} in $kind that is not itself erased", tree1.srcPos) - - private def checkNotErasedClass(tree: Tree)(using Context): tree.type = - checkNotErasedClass(tree.tpe.widen.finalResultType, tree) - tree + /** Check that initializers of erased vals and arguments to erased parameters + * are pure expressions. + */ + def checkPureErased(tree: untpd.Tree, isArgument: Boolean, isImplicit: Boolean = false)(using Context): Unit = + val tree1 = tree.asInstanceOf[tpd.Tree] + inContext(preErasureCtx): + if !tpd.isPureExpr(tree1) then + report.error(ErasedNotPure(tree1, isArgument, isImplicit), tree1.srcPos) def erasedDef(sym: Symbol)(using Context): Tree = if sym.isClass then @@ -625,7 +615,7 @@ object Erasure { * are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]]. */ override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree = - checkNotErasedClass(tree.withType(erasure(tree.typeOpt))) + tree.withType(erasure(tree.typeOpt)) /** This override is only needed to semi-erase type ascriptions */ override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = @@ -644,7 +634,7 @@ object Erasure { if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt) else if (tree.const.tag == Constants.ClazzTag) - checkNotErasedClass(clsOf(tree.const.typeValue)) + clsOf(tree.const.typeValue) else super.typedLiteral(tree) @@ -848,7 +838,13 @@ object Erasure { val origFunType = origFun.tpe.widen(using preErasureCtx) val ownArgs = origFunType match case mt: MethodType if mt.hasErasedParams => - args.zip(mt.erasedParams).collect { case (arg, false) => arg } + args.lazyZip(mt.paramErasureStatuses).flatMap: (arg, isErased) => + if isErased then + checkPureErased(arg, isArgument = true, + isImplicit = mt.isImplicitMethod && arg.span.isSynthetic) + Nil + else + arg :: Nil case _ => args val fun1 = typedExpr(fun, AnyFunctionProto) fun1.tpe.widen match @@ -916,9 +912,10 @@ object Erasure { } override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = - if (sym.isEffectivelyErased) erasedDef(sym) - else - checkNotErasedClass(sym.info, vdef) + if sym.isEffectivelyErased then + checkPureErased(vdef.rhs, isArgument = false) + erasedDef(sym) + else trace(i"erasing $vdef"): super.typedValDef(untpd.cpy.ValDef(vdef)( tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym) @@ -930,7 +927,6 @@ object Erasure { if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then erasedDef(sym) else - checkNotErasedClass(sym.info.finalResultType, ddef) val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType var vparams = outerParamDefs(sym) ::: ddef.paramss.collect { @@ -1049,29 +1045,24 @@ object Erasure { adaptClosure(implClosure) } - override def typedNew(tree: untpd.New, pt: Type)(using Context): Tree = - checkNotErasedClass(super.typedNew(tree, pt)) - override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree = EmptyTree override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree = - if cls.is(Flags.Erased) then erasedDef(cls) - else - val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef, cls): @unchecked - // In the case where a trait extends a class, we need to strip any non trait class from the signature - // and accept the first one (see tests/run/mixins.scala) - val newTraits = impl.parents.tail.filterConserve: tree => - def isTraitConstructor = tree match - case Trees.Block(_, expr) => // Specific management for trait constructors (see tests/pos/i9213.scala) - expr.symbol.isConstructor && expr.symbol.owner.is(Flags.Trait) - case _ => tree.symbol.isConstructor && tree.symbol.owner.is(Flags.Trait) - tree.symbol.is(Flags.Trait) || isTraitConstructor - - val newParents = - if impl.parents.tail eq newTraits then impl.parents - else impl.parents.head :: newTraits - cpy.TypeDef(typedTree)(rhs = cpy.Template(impl)(parents = newParents)) + val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef, cls): @unchecked + // In the case where a trait extends a class, we need to strip any non trait class from the signature + // and accept the first one (see tests/run/mixins.scala) + val newTraits = impl.parents.tail.filterConserve: tree => + def isTraitConstructor = tree match + case Trees.Block(_, expr) => // Specific management for trait constructors (see tests/pos/i9213.scala) + expr.symbol.isConstructor && expr.symbol.owner.is(Flags.Trait) + case _ => tree.symbol.isConstructor && tree.symbol.owner.is(Flags.Trait) + tree.symbol.is(Flags.Trait) || isTraitConstructor + + val newParents = + if impl.parents.tail eq newTraits then impl.parents + else impl.parents.head :: newTraits + cpy.TypeDef(typedTree)(rhs = cpy.Template(impl)(parents = newParents)) override def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = typed(tree.arg, pt) diff --git a/compiler/src/dotty/tools/dotc/transform/Getters.scala b/compiler/src/dotty/tools/dotc/transform/Getters.scala index 6e6d84a9eaae..11adf4da83d5 100644 --- a/compiler/src/dotty/tools/dotc/transform/Getters.scala +++ b/compiler/src/dotty/tools/dotc/transform/Getters.scala @@ -90,7 +90,7 @@ class Getters extends MiniPhase with SymTransformer { thisPhase => d1 } - private val NoGetterNeededFlags = Method | Param | JavaDefined | JavaStatic | PhantomSymbol + private val NoGetterNeededFlags = Method | Param | JavaDefined | JavaStatic | PhantomSymbol | Erased val newSetters = util.HashSet[Symbol]() diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index ce3f26071b77..b3285f62c062 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -264,7 +264,6 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => for getter <- mixin.info.decls.toList if getter.isGetter - && !getter.isEffectivelyErased && !wasOneOf(getter, Deferred) && !getter.isConstExprFinalVal yield diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 5e2ff2d43283..9f79c063dc03 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -408,7 +408,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case app: Apply => val methType = app.fun.tpe.widen.asInstanceOf[MethodType] if (methType.hasErasedParams) - for (arg, isErased) <- app.args.lazyZip(methType.erasedParams) do + for (arg, isErased) <- app.args.lazyZip(methType.paramErasureStatuses) do if isErased then if methType.isResultDependent then Checking.checkRealizable(arg.tpe, arg.srcPos, "erased argument") @@ -475,7 +475,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case tree: ValDef => annotateExperimentalCompanion(tree.symbol) registerIfHasMacroAnnotations(tree) - checkErasedDef(tree) Checking.checkPolyFunctionType(tree.tpt) val tree1 = cpy.ValDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt)) if tree1.removeAttachment(desugar.UntupledParam).isDefined then @@ -483,7 +482,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => processValOrDefDef(super.transform(tree1)) case tree: DefDef => registerIfHasMacroAnnotations(tree) - checkErasedDef(tree) Checking.checkPolyFunctionType(tree.tpt) annotateContextResults(tree) val tree1 = cpy.DefDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt)) @@ -624,21 +622,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => if sym.derivesFrom(defn.MacroAnnotationClass) && !sym.isStatic then report.error("classes that extend MacroAnnotation must not be inner/local classes", sym.srcPos) - private def checkErasedDef(tree: ValOrDefDef)(using Context): Unit = - def checkOnlyErasedParams(): Unit = tree match - case tree: DefDef => - for params <- tree.paramss; param <- params if !param.symbol.isType && !param.symbol.is(Erased) do - report.error("erased definition can only have erased parameters", param.srcPos) - case _ => - - if tree.symbol.is(Erased, butNot = Macro) then - checkOnlyErasedParams() - val tpe = tree.rhs.tpe - if tpe.derivesFrom(defn.NothingClass) then - report.error("`erased` definition cannot be implemented with en expression of type Nothing", tree.srcPos) - else if tpe.derivesFrom(defn.NullClass) then - report.error("`erased` definition cannot be implemented with en expression of type Null", tree.srcPos) - private def annotateExperimentalCompanion(sym: Symbol)(using Context): Unit = if sym.is(Module) then ExperimentalAnnotation.copy(sym.companionClass).foreach(sym.addAnnotation) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index b04b28aebd01..51ccdfe57274 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -269,7 +269,7 @@ abstract class Recheck extends Phase, SymTransformer: def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = inContext(linkConstructorParams(sym).withOwner(sym)): val resType = recheck(tree.tpt) - if tree.rhs.isEmpty || sym.isInlineMethod || sym.isEffectivelyErased + if tree.rhs.isEmpty || sym.isInlineMethod then resType else recheck(tree.rhs, resType) diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 5a63235fc3c0..0077cb969e3a 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -155,8 +155,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val needAccessor = name.isTermName // Types don't need super accessors - && !sym.isEffectivelyErased // Erased and concrete inline methods are not called at runtime - && !sym.isInlineMethod // so they don't need superaccessors. + && !sym.isInlineMethod // Inline methods are not called at runtime so they don't need superaccessors. && (clazz != currentClass || !validCurrentClass || mix.name.isEmpty && clazz.is(Trait)) if (needAccessor) atPhase(thisPhase.next)(superAccessorCall(sel, mix.name)) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 73918dcaeb84..aedb5034a9cf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -681,10 +681,11 @@ object Checking { fail(ModifierNotAllowedForDefinition(Flags.Infix, s"A top-level ${sym.showKind} cannot be infix.")) if sym.isUpdateMethod && !sym.owner.derivesFrom(defn.Caps_Mutable) then fail(em"Update methods can only be used as members of classes extending the `Mutable` trait") - checkApplicable(Erased, - !sym.is(Lazy, butNot = Given) - && !sym.isMutableVarOrAccessor - && (!sym.isType || sym.isClass)) + val unerasable = + sym.is(Method, butNot = Macro) + || sym.is(Mutable) + || sym.isType + checkApplicable(Erased, !unerasable) checkCombination(Final, Open) checkCombination(Sealed, Open) checkCombination(Final, Sealed) diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 26d03db4b7dc..55778017b76f 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -300,9 +300,9 @@ object EtaExpansion extends LiftImpure { val body = Apply(lifted, ids) if (mt.isContextualMethod) body.setApplyKind(ApplyKind.Using) val fn = - if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given), mt.erasedParams) - else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit), mt.erasedParams) - else if (mt.hasErasedParams) new untpd.FunctionWithMods(params, body, Modifiers(), mt.erasedParams) + if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given), mt.paramErasureStatuses) + else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit), mt.paramErasureStatuses) + else if (mt.hasErasedParams) new untpd.FunctionWithMods(params, body, Modifiers(), mt.paramErasureStatuses) else untpd.Function(params, body) if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e4d237072041..298ec3682daa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1325,6 +1325,7 @@ class Namer { typer: Typer => else mbr.info.ensureMethodic (EmptyFlags, mbrInfo) var mbrFlags = MandatoryExportTermFlags | maybeStable | (sym.flags & RetainedExportTermFlags) + if sym.is(Erased) then mbrFlags |= Inline if pathMethod.exists then mbrFlags |= ExtensionMethod val forwarderName = checkNoConflict(alias, span) newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) @@ -1906,6 +1907,11 @@ class Namer { typer: Typer => case _ => val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) + // Add an erased to the using clause generated from a `: Singleton` context bound + mdef.tpt match + case tpt: untpd.ContextBoundTypeTree if mbrTpe.typeSymbol == defn.SingletonClass => + sym.setFlag(Erased) + case _ => if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue)) else mbrTpe diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 761a24e10474..51cbd8df7c15 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -231,7 +231,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case PreciseConstrained(tp, true) => if tp.isSingletonBounded(frozen = false) then withNoErrors: - ref(defn.Compiletime_erasedValue).appliedToType(formal).withSpan(span) + ref(defn.Caps_erasedValue).appliedToType(formal).withSpan(span) else withErrors(i"$tp is not a singleton") case _ => @@ -240,7 +240,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val synthesizedPrecise: SpecialHandler = (formal, span) => formal match case PreciseConstrained(tp, false) => withNoErrors: - ref(defn.Compiletime_erasedValue).appliedToType(formal).withSpan(span) + ref(defn.Caps_erasedValue).appliedToType(formal).withSpan(span) case _ => EmptyTreeNoError diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7d2bf2f463ba..9099105a0da9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1770,24 +1770,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (mt.isParamDependent) report.error(em"$mt is an illegal function type because it has inter-parameter dependencies", tree.srcPos) // Restart typechecking if there are erased classes that we want to mark erased - if mt.erasedParams.zip(mt.paramInfos.map(_.isErasedClass)).exists((paramErased, classErased) => classErased && !paramErased) then - val newParams = params3.zipWithConserve(mt.paramInfos.map(_.isErasedClass)) { (arg, isErasedClass) => - if isErasedClass then arg.withAddedFlags(Erased) else arg - } - return typedDependent(newParams) - val core = - if mt.hasErasedParams then TypeTree(defn.PolyFunctionClass.typeRef) - else - val resTpt = TypeTree(mt.nonDependentResultApprox).withSpan(body.span) - val paramTpts = appDef.termParamss.head.map(p => TypeTree(p.tpt.tpe).withSpan(p.tpt.span)) - val funSym = defn.FunctionSymbol(numArgs, isContextual) - val tycon = TypeTree(funSym.typeRef) - AppliedTypeTree(tycon, paramTpts :+ resTpt) - val res = RefinedTypeTree(core, List(appDef), ctx.owner.asClass) - if isImpure then - typed(untpd.makeRetaining(untpd.TypedSplice(res), Nil, tpnme.retainsCap), pt) + if mt.paramErasureStatuses.lazyZip(mt.paramInfos).exists: (paramErased, info) => + !paramErased && info.derivesFrom(defn.ErasedClass) + then + val newParams = params3.zipWithConserve(mt.paramInfos): (param, info) => + if info.derivesFrom(defn.ErasedClass) then param.withAddedFlags(Erased) else param + typedDependent(newParams) else - res + val core = + if mt.hasErasedParams then TypeTree(defn.PolyFunctionClass.typeRef) + else + val resTpt = TypeTree(mt.nonDependentResultApprox).withSpan(body.span) + val paramTpts = appDef.termParamss.head.map(p => TypeTree(p.tpt.tpe).withSpan(p.tpt.span)) + val funSym = defn.FunctionSymbol(numArgs, isContextual) + val tycon = TypeTree(funSym.typeRef) + AppliedTypeTree(tycon, paramTpts :+ resTpt) + val res = RefinedTypeTree(core, List(appDef), ctx.owner.asClass) + if isImpure then + typed(untpd.makeRetaining(untpd.TypedSplice(res), Nil, tpnme.retainsCap), pt) + else + res end typedDependent args match { @@ -1802,7 +1804,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val result = typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funSym.typeRef), args :+ body), pt) // if there are any erased classes, we need to re-do the typecheck. result match - case r: AppliedTypeTree if r.args.exists(_.tpe.isErasedClass) => + case r: AppliedTypeTree if r.args.init.exists(_.tpe.derivesFrom(defn.ErasedClass)) => typedFunctionType(desugar.makeFunctionWithValDefs(tree, pt), pt) case _ => result } @@ -2395,7 +2397,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer untpd.ValDef( CanThrowEvidenceName.fresh(), untpd.TypeTree(defn.CanThrowClass.typeRef.appliedTo(tp)), - untpd.ref(defn.Compiletime_erasedValue)) + untpd.ref(defn.Caps_erasedValue)) .withFlags(Given | Final | Erased) .withSpan(expr.span) val caughtExceptions = @@ -2947,6 +2949,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer completeAnnotations(vdef, sym) if sym.is(Implicit) then checkImplicitConversionDefOK(sym) if sym.is(Module) then checkNoModuleClash(sym) + else if sym.info.derivesFrom(defn.ErasedClass) then + sym.setFlag(Erased) val tpt1 = checkSimpleKinded(typedType(tpt)) val rhs1 = vdef.rhs match case rhs @ Ident(nme.WILDCARD) => @@ -2974,14 +2978,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer EmptyTree def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = if !sym.info.exists then retractDefDef(sym) else ctx.profiler.onTypedDef(sym) { - - // TODO: - Remove this when `scala.language.experimental.erasedDefinitions` is no longer experimental. - // - Modify signature to `erased def erasedValue[T]: T` - if sym.eq(defn.Compiletime_erasedValue) then - // scala.compiletime.erasedValue should be `erased` but we cannot add this in the source. - // The library cannot use experimental language features, - // hence we special case it until `erased` is no longer experimental. - sym.setFlag(Erased) val DefDef(name, paramss, tpt, _) = ddef checkNonRootName(ddef.name, ddef.nameSpan) completeAnnotations(ddef, sym) @@ -3079,16 +3075,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } /** (1) Check that the signature of the class member does not return a repeated parameter type - * (2) If info is an erased class, set erased flag of member - * (3) Check that erased classes are not parameters of polymorphic functions. - * (4) Make sure the definition's symbol is `sym`. - * (5) Set the `defTree` of `sym` to be `mdef`. + * (2) Make sure the definition's symbol is `sym`. + * (3) Set the `defTree` of `sym` to be `mdef`. */ private def postProcessInfo(mdef: MemberDef, sym: Symbol)(using Context): MemberDef = if (!sym.isOneOf(Synthetic | InlineProxy | Param) && sym.info.finalResultType.isRepeatedParam) report.error(em"Cannot return repeated parameter type ${sym.info.finalResultType}", sym.srcPos) - if !sym.is(Module) && !sym.isConstructor && sym.info.finalResultType.isErasedClass then - sym.setFlag(Erased) mdef.ensureHasSym(sym) mdef.setDefTree @@ -3812,7 +3804,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } val erasedParams = pt match { - case defn.PolyFunctionOf(mt: MethodType) => mt.erasedParams + case defn.PolyFunctionOf(mt: MethodType) => mt.paramErasureStatuses case _ => paramTypes.map(_ => false) } diff --git a/compiler/src/dotty/tools/dotc/util/DiffUtil.scala b/compiler/src/dotty/tools/dotc/util/DiffUtil.scala index 31acc91caa2e..126cff9b9c65 100644 --- a/compiler/src/dotty/tools/dotc/util/DiffUtil.scala +++ b/compiler/src/dotty/tools/dotc/util/DiffUtil.scala @@ -103,7 +103,6 @@ object DiffUtil { case Deleted(str) => deleted(str) }.mkString - (expectedDiff, actualDiff) val pad = " " * 0.max(expectedSize - expected.length) expectedDiff + pad + " | " + actualDiff diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 3790174526b3..e3d5355eaffa 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -448,7 +448,7 @@ class QuoteMatcher(debug: Boolean) { def matchErasedParams(sctype: Type, pttype: Type): optional[MatchingExprs] = (sctype, pttype) match case (sctpe: MethodType, pttpe: MethodType) => - if sctpe.erasedParams.sameElements(pttpe.erasedParams) then + if sctpe.paramErasureStatuses.sameElements(pttpe.paramErasureStatuses) then matchErasedParams(sctpe.resType, pttpe.resType) else notMatched diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 852d7ee8b20f..fdd16963d33b 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2288,7 +2288,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler case _ => MethodTypeKind.Plain def param(idx: Int): TypeRepr = self.newParamRef(idx) - def erasedParams: List[Boolean] = self.erasedParams + def erasedParams: List[Boolean] = self.paramErasureStatuses def hasErasedParams: Boolean = self.hasErasedParams end extension end MethodTypeMethods diff --git a/compiler/test-resources/type-printer/test-definitions b/compiler/test-resources/type-printer/test-definitions index cdda5f65cb0e..6566496d3181 100644 --- a/compiler/test-resources/type-printer/test-definitions +++ b/compiler/test-resources/type-printer/test-definitions @@ -18,8 +18,3 @@ scala> trait E scala> implicit def x: Int = 1 def x: Int - -scala> import scala.language.experimental.erasedDefinitions - -scala> erased def y: Int = 1 -def y: Int diff --git a/docs/_docs/reference/experimental/erased-defs-spec.md b/docs/_docs/reference/experimental/erased-defs-spec.md index 1861b734bb47..4cedfcb8c5cc 100644 --- a/docs/_docs/reference/experimental/erased-defs-spec.md +++ b/docs/_docs/reference/experimental/erased-defs-spec.md @@ -4,67 +4,38 @@ title: "Erased Definitions - More Details" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/erased-defs-spec.html --- -TODO: complete ## Rules -1. `erased` is a soft modifier. It can appear: - * At the start of a parameter block of a method, function or class - * In a method definition - * In a `val` definition (but not `lazy val` or `var`) - * In a `class` or `trait` definition +1. `erased` is a soft modifier. It can appear in a `val` definition or in a parameter. - ```scala - erased val x = ... - erased def f = ... - - def g(erased x: Int) = ... - - (erased x: Int, y: Int) => ... - def h(x: (Int, erased Int) => Int) = ... - - class K(erased x: Int) { ... } - erased class E {} - ``` - - -2. A reference to an `erased` val or def can only be used +2. A reference to an `erased` value can only be used * Inside the expression of argument to an `erased` parameter * Inside the body of an `erased` `val` or `def` +3. `erased` can also be used in a function type, e.g. -3. Functions - * `(erased x1: T1, x2: T2, ..., xN: TN) => y : (erased T1, T2, ..., TN) => R` - * `(using x1: T1, erased x2: T2, ..., xN: TN) => y: (using T1, erased T2, ..., TN) => R` - * `(using erased T1) => R <:< erased T1 => R` - * `(using T1, erased T2) => R <:< (T1, erased T2) => R` - * ... - - Note that there is no subtype relation between `(erased T) => R` and `T => R` (or `(given erased T) => R` and `(given T) => R`). The `erased` parameters must match exactly in their respective positions. + * `(erased T1, T2) => R` + * `(x: T1, y: erased T2) ?=> T` + Note that there is no subtype relation between `(erased T) => R` and `T => R` (or `(erased T) ?=> R` and `T ?=> R`). The `erased` parameters must match exactly in their respective positions. 4. Eta expansion if `def f(erased x: T): U` then `f: (erased T) => U`. - 5. Erasure semantics * All `erased` parameters are removed from the function * All argument to `erased` parameters are not passed to the function - * All `erased` definitions are removed - * `(erased ET1, erased ET2, T1, ..., erased ETN, TM) => R` are erased to `(T1, ..., TM) => R`. - * `(given erased ET1, erased ET2, T1, ..., erased ETN, TM) => R` are erased to `(given T1, ..., TM) => R`. - + * All `erased` value definitions are removed + * All `erased` argument types are removed from a function type 6. Overloading Method with `erased` parameters will follow the normal overloading constraints after erasure. - 7. Overriding * Member definitions overriding each other must both be `erased` or not be `erased`. * `def foo(x: T): U` cannot be overridden by `def foo(erased x: T): U` and vice-versa. 8. Type Restrictions - * For dependent functions, `erased` parameters are limited to realizable types, that is, types that are inhabited by non-null values. - This restriction stops us from using a bad bound introduced by an erased value, which leads to unsoundness (see #4060). * Polymorphic functions with erased parameters are currently not supported, and will be rejected by the compiler. This is purely an implementation restriction, and might be lifted in the future. diff --git a/docs/_docs/reference/experimental/erased-defs.md b/docs/_docs/reference/experimental/erased-defs.md index d266cd6c9d19..426628edef13 100644 --- a/docs/_docs/reference/experimental/erased-defs.md +++ b/docs/_docs/reference/experimental/erased-defs.md @@ -4,54 +4,98 @@ title: "Erased Definitions" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/erased-defs.html --- -`erased` is a modifier that expresses that some definition or expression is erased by the compiler instead of being represented in the compiled output. It is not yet part of the Scala language standard. To enable `erased`, turn on the language feature +`erased` is a modifier that expresses that some value or parameter is erased by the compiler instead of being represented in the compiled output. It is not yet part of the Scala language standard. To enable `erased`, turn on the language feature [`experimental.erasedDefinitions`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$experimental$$erasedDefinitions$.html). This can be done with a language import ```scala import scala.language.experimental.erasedDefinitions ``` or by setting the command line option `-language:experimental.erasedDefinitions`. -Erased definitions must be in an experimental scope (see [Experimental definitions](../other-new-features/experimental-defs.md)). -## Why erased terms? +## Introduction -Let's describe the motivation behind erased terms with an example. In the -following we show a simple state machine which can be in a state `On` or `Off`. -The machine can change state from `Off` to `On` with `turnedOn` only if it is -currently `Off`. This last constraint is captured with the `IsOff[S]` contextual -evidence which only exists for `IsOff[Off]`. For example, not allowing calling -`turnedOn` on in an `On` state as we would require an evidence of type -`IsOff[On]` that will not be found. +## Why erased? +Sometimes, we need a value only to present evidence that some type can be constructed, whereas at runtime that value would not be referenced. For example, say we want to make Java serialization safe. This means that, when serializing values of some type, we want to have evidence that serialization of such a type will not fail at runtime. Java defines the `java.io.Serializable` interface to mark extending types as serializable. But this alone is not safe, since a `Serializable` class might well have unserializable fields. For instance Scala's `List` extends `Serializable` since we want to be able to serialize `List` data. But a particular list might have elements that are not serializable, for instance it might be a list of functions. If we try to serialize such a value, a `NotSerializableException` will be thrown. + +We can make serialization safe by defining an additional type class that has instances precisely for those types that are deeply serializable. For instance, like this: ```scala -sealed trait State -final class On extends State -final class Off extends State +/** Type class for types that are deeply serializable */ +trait CanSerialize[T] -@implicitNotFound("State must be Off") -class IsOff[S <: State] -object IsOff: - given isOff: IsOff[Off] = new IsOff[Off] +inline given CanSerialize[String] = CanSerialize() +inline given [T: CanSerialize] => CanSerialize[List[T]] = CanSerialize() +``` +We find a given instance of `CanSerialize` for strings, since strings are serializable. We also find a conditional given instance that says lists are serializable if their elements are. We would assume to have further instances for all types that are serializable (perhaps conditionally). -class Machine[S <: State]: - def turnedOn(using IsOff[S]): Machine[On] = new Machine[On] +Now, we can formulate a method `safeWriteObject` that serializes an object to an `ObjectOutputStream`: +```scala +def safeWriteObject[T <: java.io.Serializable] + (out: java.io.ObjectOutputStream, x: T) + (using CanSerialize[T]): Unit = + out.writeObject(x) +``` +The method works for objects of its type parameter `T`. `T` is required to conform to `java.io.Serializable` so that we can use the `writeObject` method of the output stream `out` on it. In addition we need a type class instance `CanSerialize[T]` that serves as evidence that the Java serialization will not fail at runtime. We can specialize the method to list arguments, as in the following: +```scala +def writeList[T] + (out: java.io.ObjectOutputStream, xs: List[T]) + (using erased CanSerialize[T]): Unit = + safeWriteObject(out, xs) +``` +We can test `writeList` by applying it to different types of lists: +```scala +@main def Test(out: java.io.ObjectOutputStream) = + writeList(out, List("a", "b")) // ok + writeList(out, List[Int => Int](x => x + 1, y => y * 2)) // error +``` +The first call will pass, but the second call will be rejected with a type error: +``` +No given instance of type CanSerialize[Int => Int] was found for parameter x$3 of method writeList +``` -val m = new Machine[Off] -m.turnedOn -m.turnedOn.turnedOn // ERROR -// ^ -// State must be Off +So far, this is a standard typeclass pattern to set up evidence that certain operations can be performed safely. But there is a problem with this scheme: The type class instances are passed as +additional parameters to methods `safeWriteObject` and `writeList` even though at run-time these objects will not be used anywhere. The only role of these parameters is to provide compile-time evidence that serialization for a particular type is safe. It would be nice if we could somehow "erase" these parameters so that they do not show up at run-time. This is precisely what erased does. Using erased, our example would look like this: +```scala +import language.experimental.erasedDefinitions + +class CanSerialize[T] + +inline given CanSerialize[String] = CanSerialize() +inline given [T: CanSerialize] => CanSerialize[List[T]] = CanSerialize() + +def safeWriteObject[T <: java.io.Serializable](out: java.io.ObjectOutputStream, x: T)(using erased CanSerialize[T]) = + out.writeObject(x) + +def writeList[T](out: java.io.ObjectOutputStream, xs: List[T])(using erased CanSerialize[T]) = + safeWriteObject(out, xs) + +@main def Test(out: java.io.ObjectOutputStream) = + writeList(out, List("a", "b")) // ok + writeList(out, List[Int => Int](x => x + 1, y => y * 2)) // error +``` +Note the two parameters to `safeWriteObject` and `writeList` are now `erased`. This means the parameters and their arguments are not present in the generated code. + +A safety requirement for `erased` is that we cannot simply make up evidence. For instance, say we want to make the second `writeList` pass by making up a given of the problematic type: +```scala +writeList(out, List[Int => Int](x => x + 1, y => y * 2)) + (using null.asInstanceOfCanSerialize[Int => Int]) +``` +This is just one way to do it, here is another: +```scala +def fakeEvidence: CanSerialize[Int => Int] = fakeEvidence +writeList(out, List[Int => Int](x => x + 1, y => y * 2)) + (using fakeEvidence) ``` +To rule out these attacks, we demand that the argument to an erased parameter is +a _pure expression_. Only a few expressions in Scala are pure, including + + - constants, + - non-lazy, immutable vals, + - constructors of classes that don't have an initializer, applied to pure arguments, + - `apply` methods of case classes that don't have an initializer, applied to pure arguments. -Note that in the code above the actual context arguments for `IsOff` are never -used at runtime; they serve only to establish the right constraints at compile -time. As these terms are never used at runtime there is not real need to have -them around, but they still need to be present in some form in the generated -code to be able to do separate compilation and retain binary compatibility. We -introduce _erased terms_ to overcome this limitation: we are able to enforce the -right constrains on terms at compile time. These terms have no run time -semantics and they are completely erased. +Other function calls are not classified as pure expressions. That's why the two given instances in the erased version of our examples are inline methods. After inlining, the arguments to the erased parameters are simple class constructions of `CanSerialize` which count as pure expressions. -## How to define erased terms? +## Details Parameters of methods and functions can be declared as erased, placing `erased` in front of each erased parameter (like `inline`). @@ -74,51 +118,68 @@ def methodWithErasedInt2(erased i: Int): Int = methodWithErasedInt1(i) // OK ``` -Not only parameters can be marked as erased, `val` and `def` can also be marked -with `erased`. These will also only be usable as arguments to `erased` -parameters. +The arguments to erased parameters must be pure expressions. +```scala +def f(x: Int): Int = + if x == 0 then 1 else x * f(x - 1) + +inline def g(x: Int): Int = + if x == 0 then 1 else x * g(x - 1) + +methodWithErasedInt2(5) // ok +methodWithErasedInt2(f(5)) // error, f(22) is not a pure expression +methodWithErasedInt2(g(5)) // ok since `g` is `inline`. + +Besides parameters, `val` definitions can also be marked with `erased`. +These will also only be usable as arguments to `erased` parameters or +as part of the definitions of other erased `val`s. Furthermore, the +defining right hand side of such `val` must be a pure expression. ```scala -erased val erasedEvidence: Ev = ... +erased val erasedEvidence: Ev = Ev() methodWithErasedEv(erasedEvidence, 40) // 42 ``` -## What happens with erased values at runtime? - -As `erased` are guaranteed not to be used in computations, they can and will be -erased. +## The Erased Trait +In some cases we would expect all instances of a trait to be erased. For instance, one could argue that it does not make sense to ever have a `CanSerialize[T]` instance at runtime. In that case we +can make `CanSerialize` extend from a new trait `compiletimetime.Erased` and avoid the explicit +`erased` modifiers in erased parameters and vals. Here is an alternative version our example using this scheme: ```scala -// becomes def methodWithErasedEv(x: Int): Int at runtime -def methodWithErasedEv(x: Int, erased ev: Ev): Int = ... - -def evidence1: Ev = ... -erased def erasedEvidence2: Ev = ... // does not exist at runtime -erased val erasedEvidence3: Ev = ... // does not exist at runtime +class CanSerialize[T] extends compiletime.Erased +... +def safeWriteObject[T <: java.io.Serializable](out: java.io.ObjectOutputStream, x: T)(using CanSerialize[T]) = ... -// evidence1 is not evaluated and only `x` is passed to methodWithErasedEv -methodWithErasedEv(x, evidence1) +def writeList[T: CanSerialize](out: java.io.ObjectOutputStream, xs: List[T]) = ... ``` +Because `CanSerialize` extends `Erased` we can elide the explicit `erased` modifier in the using clause of `safeWriteObject`. It now also becomes possible to use a context bound for `CanSerialize` as is shown in the `writeList` method above. The context bound expands to a +using clause `(using CanSerialize[T])` which gets implicitly tagged with `erased`. + +## Uses of `Erased` in existing Code + + - The `CanThrow[T]` typeclass is used to declare that an can be thrown. The compiler generates a `CanThrow[E]` instances for exceptions that are handled in a `try`. Methods take an implicit `CanThrow[E]` parameter to indicate that they might throw exception `E`. `CanThrow` is declared to be an `Erased` capability class, so no actual evidence of `CanThrow` remains at run-time. -## State machine with erased evidence example + - The `CanEqual` evidence of [multiversal equality](../contextual/multiversal-equality.html) checks that two types can be compared. The actual comparison is done by the universal `equals` method of class `Object` or an overriding instance, it does not rely on the `CanEqual` value. +So far, `CanEqual` is handled specially in the compiler. With erased definitions, we could +avoid some of the special treatment by making `CanThrow` extend `compiletime.Erased`. + +- The conforms `<:<` typeclass asserts that we can prove that two types are in a subtype relation. `<:<` does offer a method to upcast values, but that could be also provided as a compiler-generated +cast operation. In that case, run-time instances of `<:<` (and also `=:=`) would be no longer needed and could be erased. + + +## Example: State machine with erased evidence The following example is an extended implementation of a simple state machine which can be in a state `On` or `Off`. The machine can change state from `Off` -to `On` with `turnedOn` only if it is currently `Off`, conversely from `On` to -`Off` with `turnedOff` only if it is currently `On`. These last constraint are -captured with the `IsOff[S]` and `IsOn[S]` given evidence only exist for -`IsOff[Off]` and `IsOn[On]`. For example, not allowing calling `turnedOff` on in -an `Off` state as we would require an evidence `IsOn[Off]` that will not be -found. - -As the given evidences of `turnedOn` and `turnedOff` are not used in the -bodies of those functions we can mark them as `erased`. This will remove the -evidence parameters at runtime, but we would still evaluate the `isOn` and -`isOff` givens that were found as arguments. As `isOn` and `isOff` are not -used except as `erased` arguments, we can mark them as `erased`, hence removing -the evaluation of the `isOn` and `isOff` evidences. +to `On` with `turnOn` only if it is currently `Off`, conversely from `On` to +`Off` with `turnOff` only if it is currently `On`. These constraints are +captured represented with two typeclass traits `IsOn[T]` and `IsOff[T]`. Two given instances for these traits exist only for the right kinds of state. There is a given instance for `IsOn[On]` and one for `IsOff[Off]` but there are no given instances for the other combinations. + +The `turnOn` and `turnOff` methods each require one of these given instances to ensure the machine is in the correct state for the operation to be allowed. +As the given instances required by `turnedOn` and `turnedOff` are not used in the bodies of those functions we can mark them as `erased`. ```scala +import language.experimental.erasedDefinitions import scala.annotation.implicitNotFound sealed trait State @@ -128,104 +189,41 @@ final class Off extends State @implicitNotFound("State must be Off") class IsOff[S <: State] object IsOff: - // will not be called at runtime for turnedOn, the - // compiler will only require that this evidence exists - given IsOff[Off] = new IsOff[Off] + inline given IsOff[Off]() @implicitNotFound("State must be On") class IsOn[S <: State] object IsOn: - // will not exist at runtime, the compiler will only - // require that this evidence exists at compile time - erased given IsOn[On] = new IsOn[On] - -class Machine[S <: State] private (): - // ev will disappear from both functions - def turnedOn(using erased ev: IsOff[S]): Machine[On] = new Machine[On] - def turnedOff(using erased ev: IsOn[S]): Machine[Off] = new Machine[Off] - -object Machine: - def newMachine(): Machine[Off] = new Machine[Off] - -@main def test = - val m = Machine.newMachine() - m.turnedOn - m.turnedOn.turnedOff - - // m.turnedOff - // ^ - // State must be On - - // m.turnedOn.turnedOn - // ^ - // State must be Off -``` - -Note that in [Compile-time operations](../metaprogramming/compiletime-ops.md#erasedvalue) we discussed `erasedValue` and inline -matches. `erasedValue` is internally implemented with `erased` (and is not experimental), so the state machine above -can be encoded as follows: - -```scala -import scala.compiletime.* - -sealed trait State -final class On extends State -final class Off extends State + inline given IsOn[On]() class Machine[S <: State]: - transparent inline def turnOn(): Machine[On] = - inline erasedValue[S] match - case _: Off => new Machine[On] - case _: On => error("Turning on an already turned on machine") - - transparent inline def turnOff(): Machine[Off] = - inline erasedValue[S] match - case _: On => new Machine[Off] - case _: Off => error("Turning off an already turned off machine") - -object Machine: - def newMachine(): Machine[Off] = - println("newMachine") - new Machine[Off] -end Machine + // ev will disappear from both functions + def turnOn(using erased IsOff[S]): Machine[On] = new Machine[On] + def turnOff(using erased IsOn[S]): Machine[Off] = new Machine[Off] @main def test = - val m = Machine.newMachine() - m.turnOn() - m.turnOn().turnOff() - m.turnOn().turnOn() // error: Turning on an already turned on machine + val m = Machine[Off]() + val m1 = m.turnOn + val m2 = m1.turnOff + m2.turnOn + + // m1.turnOn + // ^ error: State must be Off + // m2.turnOff + // ^ error: State must be On ``` +The first four lines of method `test` are all valid. The commented-out operations are invalid. The operation `m1.turnOn` is invalid since `m1` is of type `Machine[On]` and `m1.turnOn` requires the given instance `IsOff[On]` which does not exist. `m2.turnOff` is invalid by analogous reasoning. -## Erased Classes +## ErasedValue -`erased` can also be used as a modifier for a class. An erased class is intended to be used only in erased definitions. If the type of a val definition or parameter is -a (possibly aliased, refined, or instantiated) erased class, the definition is assumed to be `erased` itself. Likewise, a method with an erased class return type is assumed to be `erased` itself. Since given instances expand to vals and defs, they are also assumed to be erased if the type they produce is an erased class. Finally -function types with erased classes as arguments turn into erased function types. +The `compiletime.erasedValue` method was discussed in +[Compile-time operations](../metaprogramming/compiletime-ops.md#erasedvalue). A call to `erasedValue[T]` counts as an erased reference, so it could only be +used in an erased context, i.e. as an argument to an erased parameter or on the right-hand side of an erased `val` definition. At the same time +`erasedValue` does _not_ count as a pure expression, and for that reason cannot be part of these expressions. The net effect is that any references +to `erasedValue` must be eliminated by inlining. This is intentional: +allowing `erasedValue[T]` as a legal erased expression would undermine the safety of erased capabilities, since evidence for _any_ value of an erased type can be made up by it. -Example: -```scala -erased class CanRead - -val x: CanRead = ... // `x` is turned into an erased val -val y: CanRead => Int = ... // the function is turned into an erased function -def f(x: CanRead) = ... // `f` takes an erased parameter -def g(): CanRead = ... // `g` is turned into an erased def -given CanRead = ... // the anonymous given is assumed to be erased -``` -The code above expands to -```scala -erased class CanRead - -erased val x: CanRead = ... -val y: (erased CanRead) => Int = ... -def f(erased x: CanRead) = ... -erased def g(): CanRead = ... -erased given CanRead = ... -``` -After erasure, it is checked that no references to values of erased classes remain and that no instances of erased classes are created. So the following would be an error: -```scala -val err: Any = CanRead() // error: illegal reference to erased class CanRead -``` -Here, the type of `err` is `Any`, so `err` is not considered erased. Yet its initializing value is a reference to the erased class `CanRead`. +As an escape hatch, there also a method `unsafeErasedValue` in the +`scala.caps.unsafe` object. `scala.caps.unsafe.unsafeErasedValue[T]` does count as a pure expression for every type `T`, so it can be used in an erased context. But it should be used only if we can prove by other means that the established erased evidence is valid. [More Details](./erased-defs-spec.md) diff --git a/library/src/scala/CanThrow.scala b/library/src/scala/CanThrow.scala index 485dcecb37df..e1b00bcb395b 100644 --- a/library/src/scala/CanThrow.scala +++ b/library/src/scala/CanThrow.scala @@ -8,9 +8,9 @@ import annotation.{implicitNotFound, experimental, capability} */ @experimental @implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - Adding a using clause `(using CanThrow[${E}])` to the definition of the enclosing method\n - Adding `throws ${E}` clause after the result type of the enclosing method\n - Wrapping this piece of code with a `try` block that catches ${E}") -erased class CanThrow[-E <: Exception] extends caps.SharedCapability +class CanThrow[-E <: Exception] extends caps.SharedCapability, compiletime.Erased @experimental object unsafeExceptions: - given canThrowAny: CanThrow[Exception] = compiletime.erasedValue + inline given canThrowAny: CanThrow[Exception] = caps.unsafe.unsafeErasedValue diff --git a/library/src/scala/Precise.scala b/library/src/scala/Precise.scala index aad42ca8950f..f8a8dd6b47f4 100644 --- a/library/src/scala/Precise.scala +++ b/library/src/scala/Precise.scala @@ -7,5 +7,5 @@ import language.experimental.erasedDefinitions * in precise mode. This means that singleton types and union types are not * widened. */ -@experimental erased trait Precise: +@experimental trait Precise extends compiletime.Erased: type Self diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala index fedfd7400e25..98df6ce5219c 100644 --- a/library/src/scala/caps/package.scala +++ b/library/src/scala/caps/package.scala @@ -110,6 +110,13 @@ object internal: */ final class inferredDepFun extends annotation.StaticAnnotation + /** An erasedValue issued internally by the compiler. Unlike the + * user-accessible compiletime.erasedValue, this version is assumed + * to be a pure expression, hence capability safe. The compiler generates this + * version only where it is known that a value can be generated. + */ + def erasedValue[T]: T = ??? + end internal @experimental @@ -135,4 +142,11 @@ object unsafe: */ def unsafeAssumeSeparate(op: Any): op.type = op + /** An unsafe variant of erasedValue that can be used as an escape hatch. Unlike the + * user-accessible compiletime.erasedValue, this version is assumed + * to be a pure expression, hence capability safe. But there is no proof + * of realizability, hence it is unsafe. + */ + def unsafeErasedValue[T]: T = ??? + end unsafe diff --git a/library/src/scala/compiletime/Erased.scala b/library/src/scala/compiletime/Erased.scala new file mode 100644 index 000000000000..665639322122 --- /dev/null +++ b/library/src/scala/compiletime/Erased.scala @@ -0,0 +1,7 @@ +package scala.compiletime +import annotation.experimental + +/** A marker trait for erased values. vals or parameters whose type extends + * `Erased` get an implicit `erased` modifier. + */ +@experimental trait Erased \ No newline at end of file diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 8215ae2452a3..1a161ebd4a03 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -23,7 +23,6 @@ import annotation.{compileTimeOnly, experimental} * the branches. * @syntax markdown */ -// TODO add `erased` once it is not an experimental feature anymore def erasedValue[T]: T = erasedValue[T] /** Used as the initializer of a mutable class or object field, like this: diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala index 94c444b0feb9..3720d170eb26 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala @@ -284,7 +284,8 @@ class CompletionOverrideSuite extends BaseCompletionSuite: includeDetail = false ) - @Test def `mutable` = + // Disabled since the test is flaky @Test + def `mutable` = checkEdit( """|abstract class Mutable { | def foo: scala.collection.mutable.Set[Int] diff --git a/tests/init/warn/inner30.scala b/tests/init/warn/inner30.scala index d9b1eec3d6b1..1fd112579263 100644 --- a/tests/init/warn/inner30.scala +++ b/tests/init/warn/inner30.scala @@ -8,7 +8,7 @@ class Scanners { class Scanner { def foo() = - Conc(Run('a', 3), Run('b', 4)) + Conc(Run('a', 3), Run('b', 4)) // warn new LookAheadScanner class LookAheadScanner() extends Scanner diff --git a/tests/neg/erased-1.scala b/tests/invalid/neg/erased-1.scala similarity index 100% rename from tests/neg/erased-1.scala rename to tests/invalid/neg/erased-1.scala diff --git a/tests/neg/erased-2.scala b/tests/invalid/neg/erased-2.scala similarity index 100% rename from tests/neg/erased-2.scala rename to tests/invalid/neg/erased-2.scala diff --git a/tests/neg/erased-3.scala b/tests/invalid/neg/erased-3.scala similarity index 100% rename from tests/neg/erased-3.scala rename to tests/invalid/neg/erased-3.scala diff --git a/tests/neg/erased-args-lifted.scala b/tests/invalid/neg/erased-args-lifted.scala similarity index 87% rename from tests/neg/erased-args-lifted.scala rename to tests/invalid/neg/erased-args-lifted.scala index dfa7b74ee3d4..a2f6a654429f 100644 --- a/tests/neg/erased-args-lifted.scala +++ b/tests/invalid/neg/erased-args-lifted.scala @@ -2,7 +2,7 @@ object Test { def foo(a: Int)(b: Int, c: Int) = 42 - erased def bar(erased i: Int): Int = { + inline def bar(erased i: Int): Int = { println(1) 42 } diff --git a/tests/neg/erased-implicit.scala b/tests/invalid/neg/erased-implicit.scala similarity index 100% rename from tests/neg/erased-implicit.scala rename to tests/invalid/neg/erased-implicit.scala diff --git a/tests/neg/erased-inheritance.scala b/tests/invalid/neg/erased-inheritance.scala similarity index 100% rename from tests/neg/erased-inheritance.scala rename to tests/invalid/neg/erased-inheritance.scala diff --git a/tests/neg/erased-params.scala b/tests/invalid/neg/erased-params.scala similarity index 100% rename from tests/neg/erased-params.scala rename to tests/invalid/neg/erased-params.scala diff --git a/tests/neg/safeThrowsStrawman2.scala b/tests/invalid/neg/safeThrowsStrawman2.scala similarity index 94% rename from tests/neg/safeThrowsStrawman2.scala rename to tests/invalid/neg/safeThrowsStrawman2.scala index 8d95494e30e0..c6ef62317c6e 100644 --- a/tests/neg/safeThrowsStrawman2.scala +++ b/tests/invalid/neg/safeThrowsStrawman2.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions object scalax: - erased class CanThrow[E <: Exception] + class CanThrow[E <: Exception] extends compiletime.Erased type CTF = CanThrow[Fail] infix type raises[R, E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/pos/i11743.scala b/tests/invalid/pos/i11743.scala similarity index 81% rename from tests/pos/i11743.scala rename to tests/invalid/pos/i11743.scala index ae524ca01ad6..3114383b3433 100644 --- a/tests/pos/i11743.scala +++ b/tests/invalid/pos/i11743.scala @@ -2,7 +2,7 @@ import language.experimental.erasedDefinitions import scala.compiletime.erasedValue type UnivEq[A] object UnivEq: - erased def force[A]: UnivEq[A] = erasedValue + inline def force[A]: UnivEq[A] = erasedValue extension [A](erased proof: UnivEq[A]) inline def univEq(a: A, b: A): Boolean = a == b diff --git a/tests/pos/i17584a.scala b/tests/invalid/pos/i17584a.scala similarity index 100% rename from tests/pos/i17584a.scala rename to tests/invalid/pos/i17584a.scala diff --git a/tests/run/erased-1.check b/tests/invalid/run/erased-1.check similarity index 100% rename from tests/run/erased-1.check rename to tests/invalid/run/erased-1.check diff --git a/tests/run/erased-1.scala b/tests/invalid/run/erased-1.scala similarity index 100% rename from tests/run/erased-1.scala rename to tests/invalid/run/erased-1.scala diff --git a/tests/run/erased-10.check b/tests/invalid/run/erased-10.check similarity index 100% rename from tests/run/erased-10.check rename to tests/invalid/run/erased-10.check diff --git a/tests/run/erased-10.scala b/tests/invalid/run/erased-10.scala similarity index 92% rename from tests/run/erased-10.scala rename to tests/invalid/run/erased-10.scala index 004d07b4de37..ce8c8a42de4c 100644 --- a/tests/run/erased-10.scala +++ b/tests/invalid/run/erased-10.scala @@ -10,7 +10,7 @@ object Test { println("pacFun4") } - erased def inky: Int = { + inline def inky: Int = { println("inky") // in erased function 42 } diff --git a/tests/run/erased-11.check b/tests/invalid/run/erased-11.check similarity index 100% rename from tests/run/erased-11.check rename to tests/invalid/run/erased-11.check diff --git a/tests/run/erased-11.scala b/tests/invalid/run/erased-11.scala similarity index 100% rename from tests/run/erased-11.scala rename to tests/invalid/run/erased-11.scala diff --git a/tests/run/erased-12.check b/tests/invalid/run/erased-12.check similarity index 100% rename from tests/run/erased-12.check rename to tests/invalid/run/erased-12.check diff --git a/tests/run/erased-12.scala b/tests/invalid/run/erased-12.scala similarity index 100% rename from tests/run/erased-12.scala rename to tests/invalid/run/erased-12.scala diff --git a/tests/run/erased-13.check b/tests/invalid/run/erased-13.check similarity index 100% rename from tests/run/erased-13.check rename to tests/invalid/run/erased-13.check diff --git a/tests/run/erased-13.scala b/tests/invalid/run/erased-13.scala similarity index 100% rename from tests/run/erased-13.scala rename to tests/invalid/run/erased-13.scala diff --git a/tests/run/erased-14.check b/tests/invalid/run/erased-14.check similarity index 100% rename from tests/run/erased-14.check rename to tests/invalid/run/erased-14.check diff --git a/tests/run/erased-14.scala b/tests/invalid/run/erased-14.scala similarity index 100% rename from tests/run/erased-14.scala rename to tests/invalid/run/erased-14.scala diff --git a/tests/run/erased-16.check b/tests/invalid/run/erased-16.check similarity index 100% rename from tests/run/erased-16.check rename to tests/invalid/run/erased-16.check diff --git a/tests/run/erased-16.scala b/tests/invalid/run/erased-16.scala similarity index 100% rename from tests/run/erased-16.scala rename to tests/invalid/run/erased-16.scala diff --git a/tests/run/erased-17.check b/tests/invalid/run/erased-17.check similarity index 100% rename from tests/run/erased-17.check rename to tests/invalid/run/erased-17.check diff --git a/tests/run/erased-17.scala b/tests/invalid/run/erased-17.scala similarity index 100% rename from tests/run/erased-17.scala rename to tests/invalid/run/erased-17.scala diff --git a/tests/run/erased-22.check b/tests/invalid/run/erased-22.check similarity index 100% rename from tests/run/erased-22.check rename to tests/invalid/run/erased-22.check diff --git a/tests/run/erased-22.scala b/tests/invalid/run/erased-22.scala similarity index 100% rename from tests/run/erased-22.scala rename to tests/invalid/run/erased-22.scala diff --git a/tests/run/erased-27.check b/tests/invalid/run/erased-27.check similarity index 100% rename from tests/run/erased-27.check rename to tests/invalid/run/erased-27.check diff --git a/tests/run/erased-27.scala b/tests/invalid/run/erased-27.scala similarity index 100% rename from tests/run/erased-27.scala rename to tests/invalid/run/erased-27.scala diff --git a/tests/run/erased-28.check b/tests/invalid/run/erased-28.check similarity index 100% rename from tests/run/erased-28.check rename to tests/invalid/run/erased-28.check diff --git a/tests/run/erased-28.scala b/tests/invalid/run/erased-28.scala similarity index 100% rename from tests/run/erased-28.scala rename to tests/invalid/run/erased-28.scala diff --git a/tests/run/erased-3.check b/tests/invalid/run/erased-3.check similarity index 100% rename from tests/run/erased-3.check rename to tests/invalid/run/erased-3.check diff --git a/tests/run/erased-3.scala b/tests/invalid/run/erased-3.scala similarity index 100% rename from tests/run/erased-3.scala rename to tests/invalid/run/erased-3.scala diff --git a/tests/run/erased-4.check b/tests/invalid/run/erased-4.check similarity index 100% rename from tests/run/erased-4.check rename to tests/invalid/run/erased-4.check diff --git a/tests/run/erased-4.scala b/tests/invalid/run/erased-4.scala similarity index 100% rename from tests/run/erased-4.scala rename to tests/invalid/run/erased-4.scala diff --git a/tests/run/erased-5.check b/tests/invalid/run/erased-5.check similarity index 100% rename from tests/run/erased-5.check rename to tests/invalid/run/erased-5.check diff --git a/tests/run/erased-5.scala b/tests/invalid/run/erased-5.scala similarity index 100% rename from tests/run/erased-5.scala rename to tests/invalid/run/erased-5.scala diff --git a/tests/run/erased-6.check b/tests/invalid/run/erased-6.check similarity index 100% rename from tests/run/erased-6.check rename to tests/invalid/run/erased-6.check diff --git a/tests/run/erased-6.scala b/tests/invalid/run/erased-6.scala similarity index 100% rename from tests/run/erased-6.scala rename to tests/invalid/run/erased-6.scala diff --git a/tests/run/erased-8.check b/tests/invalid/run/erased-8.check similarity index 100% rename from tests/run/erased-8.check rename to tests/invalid/run/erased-8.check diff --git a/tests/run/erased-8.scala b/tests/invalid/run/erased-8.scala similarity index 100% rename from tests/run/erased-8.scala rename to tests/invalid/run/erased-8.scala diff --git a/tests/run/erased-9.check b/tests/invalid/run/erased-9.check similarity index 100% rename from tests/run/erased-9.check rename to tests/invalid/run/erased-9.check diff --git a/tests/run/erased-9.scala b/tests/invalid/run/erased-9.scala similarity index 100% rename from tests/run/erased-9.scala rename to tests/invalid/run/erased-9.scala diff --git a/tests/run/erased-class-are-erased.check b/tests/invalid/run/erased-class-are-erased.check similarity index 100% rename from tests/run/erased-class-are-erased.check rename to tests/invalid/run/erased-class-are-erased.check diff --git a/tests/run/erased-class-are-erased.scala b/tests/invalid/run/erased-class-are-erased.scala similarity index 100% rename from tests/run/erased-class-are-erased.scala rename to tests/invalid/run/erased-class-are-erased.scala diff --git a/tests/run/erased-frameless.check b/tests/invalid/run/erased-frameless.check similarity index 100% rename from tests/run/erased-frameless.check rename to tests/invalid/run/erased-frameless.check diff --git a/tests/run/erased-frameless.scala b/tests/invalid/run/erased-frameless.scala similarity index 88% rename from tests/run/erased-frameless.scala rename to tests/invalid/run/erased-frameless.scala index fe654639492a..a366e705840c 100644 --- a/tests/run/erased-frameless.scala +++ b/tests/invalid/run/erased-frameless.scala @@ -28,7 +28,7 @@ trait Dataset[T] { // Use c.label to do an untyped select on actual Spark Dataset, and // cast the result to TypedDataset[A] - def col[S <: String, A](s: S) (using erased ev: Exists[T, s.type, A]) = + inline def col[S <: String, A](s: S) (using erased ev: Exists[T, s.type, A]) = new Column[T, A](s) // ev is only here to check than this is safe, it's never used at runtime! def collect(): Vector[T] @@ -71,17 +71,17 @@ case class Column[T, A](label: String) trait Exists[T, K, V] object Exists { - implicit def derive[T, H <: HList, K, V](implicit g: LabelledGeneric[T] { type Repr = H }, s: Selector[H, K, V]): Exists[T, K, V] = { + inline implicit def derive[T, H <: HList, K, V](implicit g: LabelledGeneric[T] { type Repr = H }, s: Selector[H, K, V]): Exists[T, K, V] = { println("Exists.derive") null } - implicit def caseFound[T <: HList, K <: String, V]: Selector[R[K, V] :: T, K, V] = { + inline implicit def caseFound[T <: HList, K <: String, V]: Selector[R[K, V] :: T, K, V] = { println("Selector.caseFound") null } - implicit def caseRecur[H, T <: HList, K <: String, V](implicit i: Selector[T, K, V]): Selector[H :: T, K, V] = { + inline implicit def caseRecur[H, T <: HList, K <: String, V](implicit i: Selector[T, K, V]): Selector[H :: T, K, V] = { println("Selector.caseRecur") null } diff --git a/tests/run/erased-select-prefix.check b/tests/invalid/run/erased-select-prefix.check similarity index 100% rename from tests/run/erased-select-prefix.check rename to tests/invalid/run/erased-select-prefix.check diff --git a/tests/run/erased-select-prefix.scala b/tests/invalid/run/erased-select-prefix.scala similarity index 77% rename from tests/run/erased-select-prefix.scala rename to tests/invalid/run/erased-select-prefix.scala index b877a0d209d7..06ed46d5ccce 100644 --- a/tests/run/erased-select-prefix.scala +++ b/tests/invalid/run/erased-select-prefix.scala @@ -29,9 +29,9 @@ object Test { def bar(erased i: Int): Unit = () - erased def foo0: Int = 0 - erased def foo1(): Int = 1 - erased def foo2[T]: Int = 2 - erased def foo3[T](): Int = 3 + inline def foo0: Int = 0 + inline def foo1(): Int = 1 + inline def foo2[T]: Int = 2 + inline def foo3[T](): Int = 3 } diff --git a/tests/run/erased-value-class.check b/tests/invalid/run/erased-value-class.check similarity index 100% rename from tests/run/erased-value-class.check rename to tests/invalid/run/erased-value-class.check diff --git a/tests/run/erased-value-class.scala b/tests/invalid/run/erased-value-class.scala similarity index 100% rename from tests/run/erased-value-class.scala rename to tests/invalid/run/erased-value-class.scala diff --git a/tests/run/polymorphic-erased-functions.scala b/tests/invalid/run/polymorphic-erased-functions.scala similarity index 100% rename from tests/run/polymorphic-erased-functions.scala rename to tests/invalid/run/polymorphic-erased-functions.scala diff --git a/tests/neg-custom-args/captures/erased-methods2.scala b/tests/neg-custom-args/captures/erased-methods2.scala index 6e111f1702da..4eda00d1b4ac 100644 --- a/tests/neg-custom-args/captures/erased-methods2.scala +++ b/tests/neg-custom-args/captures/erased-methods2.scala @@ -6,7 +6,7 @@ class Ex1 extends Exception("Ex1") class Ex2 extends Exception("Ex2") class Ex3 extends Exception("Ex3") -erased class CT[-E <: Exception] extends caps.Capability +class CT[-E <: Exception] extends caps.Capability, compiletime.Erased def Throw[Ex <: Exception](ex: Ex)(using CT[Ex]^): Nothing = ??? diff --git a/tests/neg/erased-6.scala b/tests/neg/erased-6.scala index 4585ab876b3d..76fa1b937f00 100644 --- a/tests/neg/erased-6.scala +++ b/tests/neg/erased-6.scala @@ -1,7 +1,7 @@ //> using options -language:experimental.erasedDefinitions object Test { - erased def foo: Foo = new Foo + erased val foo: Foo = new Foo // error, Foo is not noInits foo.x() // error foo.y // error foo.z // error diff --git a/tests/neg/erased-assign.scala b/tests/neg/erased-assign.scala index 5026ca3f1856..61c8802e576e 100644 --- a/tests/neg/erased-assign.scala +++ b/tests/neg/erased-assign.scala @@ -4,7 +4,7 @@ object Test { var i: Int = 1 def foo(erased a: Int): Int = { i = a // error - erased def r = { + inline def r = { i = a () } diff --git a/tests/neg/erased-can-serialize.scala b/tests/neg/erased-can-serialize.scala new file mode 100644 index 000000000000..13d9ad072f01 --- /dev/null +++ b/tests/neg/erased-can-serialize.scala @@ -0,0 +1,17 @@ +import language.experimental.erasedDefinitions + +class CanSerialize[T] + +inline given CanSerialize[String] = CanSerialize() +inline given [T: CanSerialize] => CanSerialize[List[T]] = CanSerialize() + +def safeWriteObject[T <: java.io.Serializable](out: java.io.ObjectOutputStream, x: T)(using erased CanSerialize[T]) = + out.writeObject(x) + +def writeList[T](out: java.io.ObjectOutputStream, xs: List[T])(using erased CanSerialize[T]) = + safeWriteObject(out, xs) + +@main def Test(out: java.io.ObjectOutputStream) = + writeList(out, List(List("a", "b"))) + writeList(out, List[Int => Int](x => x + 1, y => y * 2)) // error + diff --git a/tests/neg/erased-class.scala b/tests/neg/erased-class.scala index 96a1c8769bb1..53dc08a38ccd 100644 --- a/tests/neg/erased-class.scala +++ b/tests/neg/erased-class.scala @@ -1,10 +1,10 @@ import language.experimental.erasedDefinitions import scala.annotation.compileTimeOnly -erased class AA -erased class BB extends AA // ok +class AA extends compiletime.Erased +class BB extends AA // ok @main def Test = - val f1: Array[AA] = compiletime.erasedValue // error // error - def f2(x: Int): Array[AA] = compiletime.erasedValue // error // error - def bar: AA = compiletime.erasedValue // ok - val baz: AA = compiletime.erasedValue // ok + val f1: Array[AA] = caps.unsafe.unsafeErasedValue // error + def f2(x: Int): Array[AA] = caps.unsafe.unsafeErasedValue // error + def bar: AA = caps.unsafe.unsafeErasedValue // error + val baz: AA = caps.unsafe.unsafeErasedValue // ok diff --git a/tests/neg/erased-path.scala b/tests/neg/erased-path.scala index ece90e563483..6666165d5cc6 100644 --- a/tests/neg/erased-path.scala +++ b/tests/neg/erased-path.scala @@ -6,6 +6,6 @@ trait Obj { erased val s: Sys lazy val t: Sys - type S = s.X // error: not a legal path, since nonfinal + type S = s.X // now OK, was error: not a legal path, since nonfinal type T = t.X // error: not a legal path, since nonfinal } \ No newline at end of file diff --git a/tests/neg/erasedValueb.check b/tests/neg/erasedValueb.check new file mode 100644 index 000000000000..f5765064a036 --- /dev/null +++ b/tests/neg/erasedValueb.check @@ -0,0 +1,10 @@ +-- Error: tests/neg/erasedValueb.scala:7:7 ----------------------------------------------------------------------------- +7 | foo0(erasedValue[Int]) // error + | ^^^^^^^^^^^ + | method erasedValue is declared as `erased`, but is in fact used +-- [E216] Type Error: tests/neg/erasedValueb.scala:8:18 ---------------------------------------------------------------- +8 | foo1(erasedValue[Int]) // error + | ^^^^^^^^^^^^^^^^ + | argument to an erased parameter fails to be a pure expression + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/erasedValueb.scala b/tests/neg/erasedValueb.scala index 5c1f1d359e93..a25cf66ec3cb 100644 --- a/tests/neg/erasedValueb.scala +++ b/tests/neg/erasedValueb.scala @@ -5,5 +5,5 @@ object Test { def foo0(a: Int): Int = 3 def foo1(erased a: Int): Int = 3 foo0(erasedValue[Int]) // error - foo1(erasedValue[Int]) + foo1(erasedValue[Int]) // error } diff --git a/tests/neg/experimental-imports.scala b/tests/neg/experimental-imports.scala index e3a91be45f08..10e655ddf3b2 100644 --- a/tests/neg/experimental-imports.scala +++ b/tests/neg/experimental-imports.scala @@ -8,14 +8,14 @@ object Object1: import language.experimental.namedTypeArguments import language.experimental.genericNumberLiterals import language.experimental.erasedDefinitions - erased def f = 1 + erased val f = 1 object Object2: import language.experimental.fewerBraces // error import language.experimental.namedTypeArguments // error import language.experimental.genericNumberLiterals // error import language.experimental.erasedDefinitions // error - erased def f = 1 + erased val f = 1 @experimental object Class1: @@ -23,14 +23,14 @@ object Class1: import language.experimental.namedTypeArguments import language.experimental.genericNumberLiterals import language.experimental.erasedDefinitions - erased def f = 1 + erased val f = 1 object Class2: import language.experimental.fewerBraces // error import language.experimental.namedTypeArguments // error import language.experimental.genericNumberLiterals // error import language.experimental.erasedDefinitions // error - erased def f = 1 + erased val f = 1 @experimental def fun1 = @@ -38,11 +38,11 @@ def fun1 = import language.experimental.namedTypeArguments import language.experimental.genericNumberLiterals import language.experimental.erasedDefinitions - erased def f = 1 + erased val f = 1 def fun2 = import language.experimental.fewerBraces // error import language.experimental.namedTypeArguments // error import language.experimental.genericNumberLiterals // error import language.experimental.erasedDefinitions // error - erased def f = 1 + erased val f = 1 diff --git a/tests/neg/experimental.scala b/tests/neg/experimental.scala index f35a7ca19d7f..583a8c5aa183 100644 --- a/tests/neg/experimental.scala +++ b/tests/neg/experimental.scala @@ -13,7 +13,7 @@ class Test1 { import scala.compiletime.erasedValue type UnivEq[A] object UnivEq: - erased def force[A]: UnivEq[A] = erasedValue + inline def force[A]: UnivEq[A] = erasedValue extension [A](erased proof: UnivEq[A]) inline def univEq(a: A, b: A): Boolean = a == b diff --git a/tests/neg/i20317a.scala b/tests/neg/i20317a.scala index d7b8b66eb80e..df0667d53ab7 100644 --- a/tests/neg/i20317a.scala +++ b/tests/neg/i20317a.scala @@ -1,5 +1,5 @@ type SemigroupStructural[A] = A & { def combine(a: A): A } def combineAll[A <: SemigroupStructural[A]]( - i: A, l: List[A] + i: A, l: List[A] // error ): A = l.foldLeft(i)(_.combine(_)) // error diff --git a/tests/neg/i23406.check b/tests/neg/i23406.check new file mode 100644 index 000000000000..a9ca83c9a8b3 --- /dev/null +++ b/tests/neg/i23406.check @@ -0,0 +1,40 @@ +-- Error: tests/neg/i23406.scala:18:7 ---------------------------------------------------------------------------------- +18 | funny[String] // error + | ^^^^^^^^^^^^^ + | value x is unusable in method Test because it refers to an erased expression + | in the selector of an inline match that reduces to + | + | { + | erased val $scrutinee1: String = compiletime.package$package.erasedValue[String] + | erased val x: String = $scrutinee1 + | { + | x:String + | } + | } + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i23406.scala:7 + 7 | case x: String => x + | ^ + -------------------------------------------------------------------------------------------------------------------- +-- Error: tests/neg/i23406.scala:19:9 ---------------------------------------------------------------------------------- +19 | problem[String] // error + | ^^^^^^^^^^^^^^^ + | value x is unusable in method Test because it refers to an erased expression + | in the selector of an inline match that reduces to + | + | { + | erased val $scrutinee2: String = compiletime.package$package.erasedValue[String] + | erased val x: String = $scrutinee2 + | { + | foo(x) + | } + | } + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i23406.scala:11 +11 | case x: String => foo(x) + | ^ + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23406.scala b/tests/neg/i23406.scala new file mode 100644 index 000000000000..ffebf0e78701 --- /dev/null +++ b/tests/neg/i23406.scala @@ -0,0 +1,20 @@ +import language.experimental.erasedDefinitions + +def foo(erased x: String): String = "" + +inline def funny[T]: String = + inline compiletime.erasedValue[T] match + case x: String => x + +inline def problem[T]: String = + inline compiletime.erasedValue[T] match + case x: String => foo(x) + +inline def ok[T]: String = + inline compiletime.erasedValue[T] match + case x: String => "hi" + +def Test = + funny[String] // error + problem[String] // error + ok[String] diff --git a/tests/neg/i4060.scala b/tests/neg/i4060.scala index bd16ed867966..b85c1190cc3e 100644 --- a/tests/neg/i4060.scala +++ b/tests/neg/i4060.scala @@ -6,7 +6,7 @@ object App { trait A { type L >: Any} def upcast(erased a: A)(x: Any): a.L = x erased val p: A { type L <: Nothing } = p - def coerce(x: Any): Int = upcast(p)(x) // error + def coerce(x: Any): Int = upcast(p)(x) // ok? def coerceInline(x: Any): Int = upcast(compiletime.erasedValue[A {type L <: Nothing}])(x) // error @@ -14,7 +14,7 @@ object App { def upcast_dep_parameter(erased a: B)(x: a.L) : Int = x erased val q : B { type L >: Any } = compiletime.erasedValue - def coerceInlineWithB(x: Any): Int = upcast_dep_parameter(q)(x) // error + def coerceInlineWithB(x: Any): Int = upcast_dep_parameter(q)(x) // ok? def main(args: Array[String]): Unit = { println(coerce("Uh oh!")) diff --git a/tests/neg/lambda-infer.scala b/tests/neg/lambda-infer.scala index 6c3db90cb893..ed2737a6f7ad 100644 --- a/tests/neg/lambda-infer.scala +++ b/tests/neg/lambda-infer.scala @@ -2,7 +2,7 @@ type F = (x: Int, erased y: Int) => Int -erased class A +class A extends compiletime.Erased @main def Test() = val a: F = (x, y) => x + 1 // error: Expected F got (Int, Int) => Int diff --git a/tests/neg/magic-canthrow.scala b/tests/neg/magic-canthrow.scala new file mode 100644 index 000000000000..ceca68fd233a --- /dev/null +++ b/tests/neg/magic-canthrow.scala @@ -0,0 +1,11 @@ +import language.experimental.erasedDefinitions +import java.io.IOException + +class CanThrow[-E <: Exception] + +def foo[E <: Exception](e: E)(using erased CanThrow[E]): Nothing = throw e + +erased def magic[E]: E = magic // error + +def Test = foo(new IOException)(using magic) + diff --git a/tests/neg/safeThrowsStrawman.scala b/tests/neg/safeThrowsStrawman.scala index bc07eb0bb3f9..a94bec429899 100644 --- a/tests/neg/safeThrowsStrawman.scala +++ b/tests/neg/safeThrowsStrawman.scala @@ -3,7 +3,7 @@ import annotation.implicitNotFound object scalax: @implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - A using clause `(using CanThrow[${E}])`\n - A raises clause in a result type such as `X raises ${E}`\n - an enclosing `try` that catches ${E}") - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased infix type raises[R, +E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/neg/typeclass-derivation2.scala b/tests/neg/typeclass-derivation2.scala index eca11fb326ed..ba89fb4c39c8 100644 --- a/tests/neg/typeclass-derivation2.scala +++ b/tests/neg/typeclass-derivation2.scala @@ -119,7 +119,7 @@ object TypeLevel { type Subtype[t] = Type[_, t] type Supertype[t] = Type[t, _] type Exactly[t] = Type[t, t] - erased def typeOf[T]: Type[T, T] = compiletime.erasedValue + inline def typeOf[T]: Type[T, T] = compiletime.erasedValue } // An algebraic datatype diff --git a/tests/new/test.scala b/tests/new/test.scala index d350e15a8c9f..de3073216898 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1,15 +1,3 @@ -package foo - -package object bar: - opaque type O[X] >: X = X - -class Test: - import bar.O - - val x = "abc" - val y: O[String] = x - //val z: String = y - - +def foo[T: Singleton](x: T) = x diff --git a/tests/pending/neg/erased-impure.check b/tests/pending/neg/erased-impure.check new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/pending/neg/erased-impure.scala b/tests/pending/neg/erased-impure.scala new file mode 100644 index 000000000000..8dd668bbc529 --- /dev/null +++ b/tests/pending/neg/erased-impure.scala @@ -0,0 +1,44 @@ +//> using options -explain +import language.experimental.erasedDefinitions +import java.io.IOException +import caps.unsafe.unsafeErasedValue + +class CanThrow[-E <: Exception] + +def foo[E <: Exception](e: E)(using erased CanThrow[E]): Nothing = throw e + +erased val magic1: IOException = ??? // error +erased val magic2: IOException = compiletime.erasedValue[IOException] // error +erased val magic3: IOException = null.asInstanceOf[IOException] // error + +inline def inlineId[T](x: T) = x + +class C() + +def testPure[T](erased x: T): Unit = () + +case class Pair[A, B](x: A, y: B) +object Pair: + def apply(x: Int): Pair2[Int, Int] = + println("Pair2") + Pair2(x, x + 1) + +case class Box[A](x: A): + println(x) + +def Test = + foo(new IOException)(using ???) // error + foo(new IOException)(using inlineId(???)) // error + + testPure(C()) // OK + testPure(inlineId(C())) // OK + testPure(identity(C())) // error, identity is not an inline function + + testPure(Pair(unsafeErasedValue[Int], unsafeErasedValue[String])) // OK + testPure(Pair(unsafeErasedValue[Int])) // error + testPure(Box(unsafeErasedValue[Int])) // error + + + + + diff --git a/tests/pos-custom-args/captures/try.scala b/tests/pos-custom-args/captures/try.scala index 5faabecc411c..c88c842babc5 100644 --- a/tests/pos-custom-args/captures/try.scala +++ b/tests/pos-custom-args/captures/try.scala @@ -14,7 +14,7 @@ def foo(x: Boolean): Int throws Fail = if x then 1 else raise(Fail()) def handle[E <: Exception, R](op: (erased CanThrow[E]) -> R)(handler: E -> R): R = - erased val x: CanThrow[E] = ??? : CanThrow[E] + erased val x = caps.unsafe.unsafeErasedValue[CanThrow[E]] try op(x) catch case ex: E => handler(ex) diff --git a/tests/neg/erased-24.scala b/tests/pos/erased-24.scala similarity index 77% rename from tests/neg/erased-24.scala rename to tests/pos/erased-24.scala index bf2f1d21435e..410a1900a1c1 100644 --- a/tests/neg/erased-24.scala +++ b/tests/pos/erased-24.scala @@ -12,8 +12,8 @@ object Test { null.asInstanceOf[foo.X] // ok } - def fun2(erased foo: Foo)(erased bar: foo.B): bar.X = { // error - null.asInstanceOf[bar.X] // error + def fun2(erased foo: Foo)(erased bar: foo.B): bar.X = { // was error + null.asInstanceOf[bar.X] // was error } } diff --git a/tests/pos/erased-asInstanceOf.scala b/tests/pos/erased-asInstanceOf.scala index 692ff3a16b05..7029c298452c 100644 --- a/tests/pos/erased-asInstanceOf.scala +++ b/tests/pos/erased-asInstanceOf.scala @@ -11,7 +11,7 @@ object Test { val ds: Dataset = ??? - lazy val collD = new Column + val collD = new Column ds.select(collD) diff --git a/tests/pos/erased-class-as-args.scala b/tests/pos/erased-class-as-args.scala index 128cd2b818e4..c223e583aed5 100644 --- a/tests/pos/erased-class-as-args.scala +++ b/tests/pos/erased-class-as-args.scala @@ -1,8 +1,8 @@ -//> using options -language:experimental.erasedDefinitions +import language.experimental.erasedDefinitions -erased class A +class A extends compiletime.Erased -erased class B(val x: Int) extends A +class B(val x: Int) extends A type T = (x: A, y: Int) => Int diff --git a/tests/pos/erased-class-separate/A_1.scala b/tests/pos/erased-class-separate/A_1.scala index 5c874ce6d89b..778f271463da 100644 --- a/tests/pos/erased-class-separate/A_1.scala +++ b/tests/pos/erased-class-separate/A_1.scala @@ -1,3 +1,3 @@ import language.experimental.erasedDefinitions -erased class A +class A extends compiletime.Erased diff --git a/tests/pos/erased-conforms.scala b/tests/pos/erased-conforms.scala index 426490d5a53a..21456fc4848a 100644 --- a/tests/pos/erased-conforms.scala +++ b/tests/pos/erased-conforms.scala @@ -1,11 +1,11 @@ import language.experimental.erasedDefinitions -erased class ErasedTerm +class ErasedTerm extends compiletime.Erased -erased class <::<[-From, +To] extends ErasedTerm +class <::<[-From, +To] extends ErasedTerm -erased class =::=[From, To] extends (From <::< To) +class =::=[From, To] extends (From <::< To) -erased given [X] => (X =::= X) = scala.compiletime.erasedValue +inline given [X] => (X =::= X) = scala.caps.unsafe.unsafeErasedValue extension [From](x: From) inline def cast[To](using From <::< To): To = x.asInstanceOf[To] // Safe cast because we know `From <:< To` diff --git a/tests/pos/erased-export.scala b/tests/pos/erased-export.scala new file mode 100644 index 000000000000..c11e3cc57d8a --- /dev/null +++ b/tests/pos/erased-export.scala @@ -0,0 +1,9 @@ +import language.experimental.erasedDefinitions + +class C(x: Int): + erased val e = x + +class D: + val c = C(22) + export c.* + erased val x = e diff --git a/tests/neg/erased-lazy-val.scala b/tests/pos/erased-lazy-val.scala similarity index 66% rename from tests/neg/erased-lazy-val.scala rename to tests/pos/erased-lazy-val.scala index 271f87cc2cf0..e0ac0bcd9db3 100644 --- a/tests/neg/erased-lazy-val.scala +++ b/tests/pos/erased-lazy-val.scala @@ -1,5 +1,5 @@ //> using options -language:experimental.erasedDefinitions object Test { - erased lazy val i: Int = 1 // error + erased lazy val i: Int = 1 // now OK } diff --git a/tests/neg/erased-pathdep-1.scala b/tests/pos/erased-pathdep-1.scala similarity index 67% rename from tests/neg/erased-pathdep-1.scala rename to tests/pos/erased-pathdep-1.scala index 422ceb5e37fe..e696c48df328 100644 --- a/tests/neg/erased-pathdep-1.scala +++ b/tests/pos/erased-pathdep-1.scala @@ -1,16 +1,14 @@ //> using options -language:experimental.erasedDefinitions -// Could become a neg test if we had totality checking for erased arguments - object Test { fun1(new Bar) val _ = fun2(new Bar) val _ = fun3(new Bar) - def fun1[F >: Bar <: Foo](erased f: F): f.X = null.asInstanceOf[f.X] // error // error - def fun2[F >: Bar <: Foo](erased f: F)(erased bar: f.B): f.B = null.asInstanceOf[f.B] // error // error // error - def fun3[F >: Bar <: Foo](erased f: F)(erased b: f.B): b.X = null.asInstanceOf[b.X] // error // error // error + def fun1[F >: Bar <: Foo](erased f: F): f.X = null.asInstanceOf[f.X] + def fun2[F >: Bar <: Foo](erased f: F)(erased bar: f.B): f.B = null.asInstanceOf[f.B] + def fun3[F >: Bar <: Foo](erased f: F)(erased b: f.B): b.X = null.asInstanceOf[b.X] } class Foo { diff --git a/tests/neg/erased-pathdep-2.scala b/tests/pos/erased-pathdep-2.scala similarity index 81% rename from tests/neg/erased-pathdep-2.scala rename to tests/pos/erased-pathdep-2.scala index 0b50acbf3b30..8c9f7b414a98 100644 --- a/tests/neg/erased-pathdep-2.scala +++ b/tests/pos/erased-pathdep-2.scala @@ -7,8 +7,8 @@ object Test { type F >: Bar <: Foo class A(erased val f: F) { - type F1 <: f.X // error - type F2[Z <: f.X] // error + type F1 <: f.X // was error + type F2[Z <: f.X] // was error } } diff --git a/tests/pos/erased-pure.scala b/tests/pos/erased-pure.scala index 9d2b54ac02b4..e62563669e66 100644 --- a/tests/pos/erased-pure.scala +++ b/tests/pos/erased-pure.scala @@ -1,4 +1,5 @@ import language.experimental.erasedDefinitions +import caps.unsafe.unsafeErasedValue inline def id[T](x: T) = x @@ -8,6 +9,7 @@ def foo[T](erased x: T): Unit = () class Pair[A, B](x: A, y: B) +case class Pair2[A, B](x: A, y: B) def Test = foo(C()) @@ -17,7 +19,8 @@ def Test = foo(Pair(C(), "hello" + "world")) foo(id(Pair(id(C()), id("hello" + "world")))) - - + //erased val x1 = Pair(unsafeErasedValue[Int], unsafeErasedValue[String]) + //erased val x2 = Pair2(unsafeErasedValue[Int], unsafeErasedValue[String]) + erased val x3 = Tuple2(unsafeErasedValue[Int], unsafeErasedValue[String]) diff --git a/tests/neg/erased-singleton.scala b/tests/pos/erased-singleton.scala similarity index 67% rename from tests/neg/erased-singleton.scala rename to tests/pos/erased-singleton.scala index 5ffa78e24b07..f7ad5165ec0a 100644 --- a/tests/neg/erased-singleton.scala +++ b/tests/pos/erased-singleton.scala @@ -5,5 +5,5 @@ trait Sys trait Obj { erased val s: Sys - type S = s.type // error: non final + type S = s.type // now OK, was error: non final } diff --git a/tests/pos/expeimental-flag-with-lang-feature.scala b/tests/pos/expeimental-flag-with-lang-feature.scala deleted file mode 100644 index 96069c332e02..000000000000 --- a/tests/pos/expeimental-flag-with-lang-feature.scala +++ /dev/null @@ -1,10 +0,0 @@ -//> using options -experimental - -import scala.language.experimental.erasedDefinitions -import scala.language.experimental.namedTypeArguments - -erased def erasedFun(erased x: Int): Int = x - -def namedTypeArgumentsFun[T, U]: Int = - namedTypeArgumentsFun[T = Int, U = Int] - namedTypeArgumentsFun[U = Int, T = Int] diff --git a/tests/pos/experimental-erased-2.scala b/tests/pos/experimental-erased-2.scala index f3b524e18463..33bf4f4abf2b 100644 --- a/tests/pos/experimental-erased-2.scala +++ b/tests/pos/experimental-erased-2.scala @@ -3,6 +3,6 @@ import annotation.experimental @experimental object Test: - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased def other = 1 diff --git a/tests/pos/experimental-erased.scala b/tests/pos/experimental-erased.scala index 156ad639f42d..1031cf9423c2 100644 --- a/tests/pos/experimental-erased.scala +++ b/tests/pos/experimental-erased.scala @@ -2,7 +2,7 @@ import language.experimental.erasedDefinitions import annotation.experimental @experimental -erased class CanThrow[-E <: Exception](val i: Int = 0) +class CanThrow[-E <: Exception](val i: Int = 0) extends compiletime.Erased @experimental object Foo diff --git a/tests/pos/experimental-imports-top.scala b/tests/pos/experimental-imports-top.scala index 9ba2b5cd2c99..595caac66fe7 100644 --- a/tests/pos/experimental-imports-top.scala +++ b/tests/pos/experimental-imports-top.scala @@ -4,4 +4,4 @@ import language.experimental.erasedDefinitions import annotation.experimental @experimental -erased def f = 1 +erased val f = 1 diff --git a/tests/pos/experimentalErased.scala b/tests/pos/experimentalErased.scala index 358c134c714a..6045f96164a5 100644 --- a/tests/pos/experimentalErased.scala +++ b/tests/pos/experimentalErased.scala @@ -2,14 +2,9 @@ import language.experimental.erasedDefinitions import annotation.experimental @experimental -erased class Foo +class Foo extends compiletime.Erased -erased class Bar - -@experimental -erased def foo = 2 - -erased def bar = 2 +class Bar extends compiletime.Erased @experimental erased val foo2 = 2 diff --git a/tests/pos/i11864.scala b/tests/pos/i11864.scala index ba43336e13ca..0301b50d7021 100644 --- a/tests/pos/i11864.scala +++ b/tests/pos/i11864.scala @@ -40,7 +40,7 @@ final class CallbackTo[+A] { object CallbackTo { type MapGuard[A] = { type Out = A } - erased given MapGuard: [A] => MapGuard[A] = compiletime.erasedValue + inline given MapGuard: [A] => MapGuard[A] = caps.unsafe.unsafeErasedValue def traverse[A, B](ta: List[A]): CallbackTo[List[B]] = val x: CallbackTo[List[A] => List[B]] = ??? diff --git a/tests/pos/i11896.scala b/tests/pos/i11896.scala index 49e5307f1a49..a4816eb5ad18 100644 --- a/tests/pos/i11896.scala +++ b/tests/pos/i11896.scala @@ -1,7 +1,7 @@ import scala.language.experimental.erasedDefinitions type X -erased def x: X = compiletime.erasedValue +inline def x: X = caps.unsafe.unsafeErasedValue def foo(using erased X): Unit = () diff --git a/tests/pos/i13392.scala b/tests/pos/i13392.scala index 614f711eebb5..5e5e2908722e 100644 --- a/tests/pos/i13392.scala +++ b/tests/pos/i13392.scala @@ -4,7 +4,7 @@ import annotation.{implicitNotFound, experimental} @experimental @implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - A using clause `(using CanThrow[${E}])`\n - A `throws` clause in a result type such as `X throws ${E}`\n - an enclosing `try` that catches ${E}") -erased class CanThrow[-E <: Exception] +class CanThrow[-E <: Exception] extends compiletime.Erased @experimental object unsafeExceptions: diff --git a/tests/pos/i20206.scala b/tests/pos/i20206.scala index 07ef3dc0ba73..89c3c7971f01 100644 --- a/tests/pos/i20206.scala +++ b/tests/pos/i20206.scala @@ -2,7 +2,7 @@ import language.experimental.erasedDefinitions -erased trait A +trait A extends compiletime.Erased trait B def foo1: A ?=> B ?=> Nothing = ??? diff --git a/tests/pos/i5938.scala b/tests/pos/i5938.scala index 17a20dcd0f1f..f392de153b4c 100644 --- a/tests/pos/i5938.scala +++ b/tests/pos/i5938.scala @@ -1,7 +1,6 @@ import scala.language.experimental.erasedDefinitions import compiletime.summonFrom -import compiletime.erasedValue trait Link[T, A] @@ -15,7 +14,7 @@ transparent inline def link[T] = class Foo object Foo { - erased implicit val barLink: Link[Foo, Bar.type] = erasedValue + erased implicit val barLink: Link[Foo, Bar.type] = caps.unsafe.unsafeErasedValue } implicit object Bar { diff --git a/tests/pos/i6419.scala b/tests/pos/i6419.scala index 550922f48d76..44136d9e48a3 100644 --- a/tests/pos/i6419.scala +++ b/tests/pos/i6419.scala @@ -9,8 +9,4 @@ class Foo { inline def bar: Unit = { foo } - - erased def baz: Unit = { - foo - } } diff --git a/tests/pos/i7741.scala b/tests/pos/i7741.scala index af9912915cc0..981789f14e2a 100644 --- a/tests/pos/i7741.scala +++ b/tests/pos/i7741.scala @@ -3,9 +3,6 @@ import scala.language.experimental.erasedDefinitions class A1 { @native private def a: Unit } -trait A2 { - erased def i(erased a: Int): Int -} trait A3 { erased val a: Int } \ No newline at end of file diff --git a/tests/pos/i7868.scala b/tests/pos/i7868.scala deleted file mode 100644 index fa31bd131b0c..000000000000 --- a/tests/pos/i7868.scala +++ /dev/null @@ -1,42 +0,0 @@ -//> using options -language:experimental.erasedDefinitions - -import language.experimental.namedTypeArguments -import scala.compiletime.* -import scala.compiletime.ops.int.* - -final case class Coproduct[+Set, +Value, Index <: Int](value: Value & Set, index: Index) - -object Coproduct { - opaque type +:[+A, +B] = A | B - - trait At[+Set, -Value, Index <: Int] { - def cast: Value <:< Set - } - - object At { - - given atHead: [Head, Tail] => At[Head +: Tail, Head, 0]: - def cast: Head <:< Head +: Tail = summon[Head <:< Head +: Tail] - - given atTail[Head, Tail, Value, NextIndex <: Int] - (using atNext: At[Tail, Value, NextIndex]) - : At[Head +: Tail, Value, S[NextIndex]] with - val cast: Value <:< Head +: Tail = atNext.cast - - given [A] => A => (() => A) = { () => summon[A] } - } - - def upCast[A, B](a: A)(using erased evidence: (A <:< B) ): B = a.asInstanceOf[B] - - def from[Set, Value, Index <: Int](value: Value)(using erased at: At[Set, Value, Index]) : ValueOf[Index] ?=> Coproduct[Set, Value, Index] = { - Coproduct[Set, Value, Index](upCast(value: Value)(using at.cast.liftCo[[X] =>> Value & X]), valueOf[Index]) - } - -} - -object Test extends App { - import Coproduct.* - - // Error: No singleton value available for scala.compiletime.ops.int.S[scala.compiletime.ops.int.S[(0 : Int)]]. - val c = from[Set = Int +: String +: Seq[Double] +: Nothing](Nil) -} diff --git a/tests/pos/inline-match-gadt.scala b/tests/pos/inline-match-gadt.scala index cf2aae00b402..7c966f33bf48 100644 --- a/tests/pos/inline-match-gadt.scala +++ b/tests/pos/inline-match-gadt.scala @@ -2,7 +2,7 @@ import scala.language.experimental.erasedDefinitions object `inline-match-gadt` { class Exactly[T] - erased def exactType[T]: Exactly[T] = compiletime.erasedValue + inline def exactType[T]: Exactly[T] = compiletime.erasedValue inline def foo[T](t: T): T = inline exactType[T] match { diff --git a/tests/pos/matchtype.scala b/tests/pos/matchtype.scala index 21c074deafd7..90d8f0dc6400 100644 --- a/tests/pos/matchtype.scala +++ b/tests/pos/matchtype.scala @@ -1,5 +1,5 @@ import scala.language.experimental.erasedDefinitions -import compiletime.erasedValue +import caps.unsafe.unsafeErasedValue as erasedValue import compiletime.ops.int.S object Test { type T[X] = X match { diff --git a/tests/pos/phantom-Eq.scala b/tests/pos/phantom-Eq.scala index d844c4b110c6..f3a4af02a186 100644 --- a/tests/pos/phantom-Eq.scala +++ b/tests/pos/phantom-Eq.scala @@ -16,18 +16,19 @@ object PhantomEq { object EqUtil { - type PhantomEq[-L, -R] + class PhantomEq[-L, -R] type PhantomEqEq[T] = PhantomEq[T, T] + erased val phantomEq = PhantomEq[Any, Any]() extension [T](x: T) def ===[U](y: U)(using erased PhantomEq[T, U]) = x.equals(y) - erased given eqString: PhantomEqEq[String] = compiletime.erasedValue - erased given eqInt: PhantomEqEq[Int] = compiletime.erasedValue - erased given eqDouble: PhantomEqEq[Double] = compiletime.erasedValue + inline given eqString: PhantomEqEq[String] = phantomEq + inline given eqInt: PhantomEqEq[Int] = phantomEq + inline given eqDouble: PhantomEqEq[Double] = phantomEq - erased given eqByteNum: PhantomEq[Byte, Number] = compiletime.erasedValue - erased given eqNumByte: PhantomEq[Number, Byte] = compiletime.erasedValue + inline given eqByteNum: PhantomEq[Byte, Number] = phantomEq + inline given eqNumByte: PhantomEq[Number, Byte] = phantomEq - erased given eqSeq: [T, U] => (erased PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = compiletime.erasedValue + inline given eqSeq: [T, U] => (erased PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = phantomEq } diff --git a/tests/pos/phantom-Eq2/Phantom-Eq_1.scala b/tests/pos/phantom-Eq2/Phantom-Eq_1.scala index b041a4a87efe..b5021a30b09b 100644 --- a/tests/pos/phantom-Eq2/Phantom-Eq_1.scala +++ b/tests/pos/phantom-Eq2/Phantom-Eq_1.scala @@ -1,19 +1,20 @@ import scala.language.experimental.erasedDefinitions +import scala.annotation.publicInBinary /* This is a version of ../pos/phantomEq.scala that tests phantom with separate compilation */ object EqUtil { - final class PhantomEq[-L, -R] private[EqUtil]() + final class PhantomEq[-L, -R] @publicInBinary private[EqUtil]() type PhantomEqEq[T] = PhantomEq[T, T] extension [T](x: T) def ===[U] (y: U) (using erased PhantomEq[T, U]) = x.equals(y) - erased given eqString: PhantomEqEq[String] = new PhantomEq[String, String] - erased given eqInt: PhantomEqEq[Int] = new PhantomEq[Int, Int] - erased given eqDouble: PhantomEqEq[Double] = new PhantomEq[Double, Double] - erased given eqByteNum: PhantomEq[Byte, Number] = new PhantomEq[Byte, Number] - erased given eqNumByte: PhantomEq[Number, Byte] = new PhantomEq[Number, Byte] - erased given eqSeq: [T, U] => (erased eq: PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = + inline given eqString: PhantomEqEq[String] = new PhantomEq[String, String] + inline given eqInt: PhantomEqEq[Int] = new PhantomEq[Int, Int] + inline given eqDouble: PhantomEqEq[Double] = new PhantomEq[Double, Double] + inline given eqByteNum: PhantomEq[Byte, Number] = new PhantomEq[Byte, Number] + inline given eqNumByte: PhantomEq[Number, Byte] = new PhantomEq[Number, Byte] + inline given eqSeq: [T, U] => (erased eq: PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = new PhantomEq[Seq[T], Seq[U]] } diff --git a/tests/pos/phantom-Evidence.scala b/tests/pos/phantom-Evidence.scala index f56ce3b798ee..db39a7b4659e 100644 --- a/tests/pos/phantom-Evidence.scala +++ b/tests/pos/phantom-Evidence.scala @@ -1,4 +1,5 @@ import scala.language.experimental.erasedDefinitions +import annotation.publicInBinary /** In this implementation variant of =:= (called =::=) we erase all instantiations and definitions of =::= */ object WithNormalState { @@ -11,9 +12,9 @@ object WithNormalState { object Instance { def newInstance(): Instance[Off] = new Instance[Off] } - class Instance[S <: State] private { - def getOnInstance (using erased ev: S =::= Off): Instance[On] = new Instance[On] // phantom parameter ev is erased - def getOffInstance (using erased ev: S =::= On): Instance[Off] = new Instance[Off] // phantom parameter ev is erased + class Instance[S <: State] @publicInBinary private { + inline def getOnInstance (using erased ev: S =::= Off): Instance[On] = new Instance[On] // phantom parameter ev is erased + inline def getOffInstance (using erased ev: S =::= On): Instance[Off] = new Instance[Off] // phantom parameter ev is erased } def run() = { @@ -26,5 +27,5 @@ object WithNormalState { object Utils { type =::=[From, To] - erased given tpEquals: [A] => (A =::= A) = compiletime.erasedValue + inline given tpEquals: [A] => (A =::= A) = caps.unsafe.unsafeErasedValue } diff --git a/tests/pos/poly-erased-functions.scala b/tests/pos/poly-erased-functions.scala index 8c7385edb86a..50ba245e782c 100644 --- a/tests/pos/poly-erased-functions.scala +++ b/tests/pos/poly-erased-functions.scala @@ -7,7 +7,7 @@ object Test: val t1 = [X] => (erased x: X, y: Int) => y val t2 = [X] => (x: X, erased y: Int) => x - erased class A + class A extends compiletime.Erased type T3 = [X] => (x: A, y: X) => X diff --git a/tests/pos/singleton-ctx-bound.scala b/tests/pos/singleton-ctx-bound.scala index c6b0d2fb823c..a6af8f9038ab 100644 --- a/tests/pos/singleton-ctx-bound.scala +++ b/tests/pos/singleton-ctx-bound.scala @@ -1,4 +1,4 @@ -//> using options -language:experimental.modularity -source future +//> using options -language:experimental.modularity -source future -language:experimental.erasedDefinitions object Test: class Wrap[T](x: T) @@ -11,7 +11,7 @@ object Test: val x1 = f1(1) val _: Wrap[1] = x1 - def f2[T](x: T)(using Singleton { type Self = T}): Wrap[T] = Wrap(x) + def f2[T](x: T)(using erased Singleton { type Self = T}): Wrap[T] = Wrap(x) val x2 = f2(1) val _: Wrap[1] = x2 @@ -19,7 +19,7 @@ object Test: val x3 = f3(1) val _: Wrap[1] = x3 - def f4[T](x: T)(using T is Singleton): Wrap[T] = Wrap(x) + def f4[T](x: T)(using erased T is Singleton): Wrap[T] = Wrap(x) val x4 = f4(1) val _: Wrap[1] = x4 @@ -33,7 +33,7 @@ object Test: val y1 = C1("hi") val _: "hi" = y1.fld - class C2[T](x: T)(using T is Singleton): + class C2[T](x: T)(using erased T is Singleton): def fld: T = x val y2 = C2("hi") val _: "hi" = y2.fld diff --git a/tests/pos/tailrec.scala b/tests/pos/tailrec.scala index 95e667c07515..902ccbf4e6ea 100644 --- a/tests/pos/tailrec.scala +++ b/tests/pos/tailrec.scala @@ -2,7 +2,7 @@ import scala.annotation.tailrec -erased class Foo1 +class Foo1 extends compiletime.Erased class Foo2 @tailrec diff --git a/tests/run-macros/i12021/Test_2.scala b/tests/run-macros/i12021/Test_2.scala index a542b14f1175..437a18959785 100644 --- a/tests/run-macros/i12021/Test_2.scala +++ b/tests/run-macros/i12021/Test_2.scala @@ -1,6 +1,6 @@ import scala.language.experimental.erasedDefinitions -erased class EC +class EC extends compiletime.Erased class X1(implicit i: Int) class X2(using i: Int) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index fd0281c5fffc..960b0f3c5fb0 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -105,7 +105,10 @@ val experimentalDefinitionInLibrary = Set( "scala.Predef$.runtimeChecked", "scala.annotation.internal.RuntimeChecked", // New feature: SIP 61 - @unroll annotation - "scala.annotation.unroll" + "scala.annotation.unroll", + + // New feature: Erased trait + "scala.compiletime.Erased", ) diff --git a/tests/run/erased-18.scala b/tests/run/erased-18.scala index 46f7e44c7309..2e5275690ea2 100644 --- a/tests/run/erased-18.scala +++ b/tests/run/erased-18.scala @@ -11,8 +11,8 @@ object Test { )(foo) } - def foo = { - println("foo") + inline def foo = { + //println("foo") 42 } } diff --git a/tests/run/erased-machine-state.check b/tests/run/erased-machine-state.check index f9d7929a8fc9..730786f063a9 100644 --- a/tests/run/erased-machine-state.check +++ b/tests/run/erased-machine-state.check @@ -1,4 +1,3 @@ -newMachine -turnedOn turnedOn turnedOff +turnedOn diff --git a/tests/run/erased-machine-state.scala b/tests/run/erased-machine-state.scala index c84f1619366d..17bbef55b753 100644 --- a/tests/run/erased-machine-state.scala +++ b/tests/run/erased-machine-state.scala @@ -1,4 +1,4 @@ -//> using options -language:experimental.erasedDefinitions +import language.experimental.erasedDefinitions import scala.annotation.implicitNotFound @@ -8,52 +8,31 @@ final class Off extends State @implicitNotFound("State must be Off") class IsOff[S <: State] -object IsOff { - implicit def isOff: IsOff[Off] = { - println("isOff") - new IsOff[Off] - } -} +object IsOff: + inline given IsOff[Off]() @implicitNotFound("State must be On") class IsOn[S <: State] -object IsOn { - implicit def isOn: IsOn[On] = { - println("isOn") - new IsOn[On] - } -} - -class Machine[S <: State] private { - def turnedOn (using erased s: IsOff[S]): Machine[On] = { +object IsOn: + inline given IsOn[On]() + +class Machine[S <: State]: + def turnOn(using erased IsOff[S]): Machine[On] = println("turnedOn") new Machine[On] - } - def turnedOff (using erased s: IsOn[S]): Machine[Off] = { + + def turnOff (using erased IsOn[S]): Machine[Off] = println("turnedOff") new Machine[Off] - } -} -object Machine { - def newMachine(): Machine[Off] = { - println("newMachine") - new Machine[Off] - } -} - -object Test { - def main(args: Array[String]): Unit = { - val m = Machine.newMachine() - m.turnedOn - m.turnedOn.turnedOff - - // m.turnedOff - // ^ - // State must be On - - // m.turnedOn.turnedOn - // ^ - // State must be Off - } -} + +@main def Test = + val m = Machine[Off]() + val m1 = m.turnOn + val m2 = m1.turnOff + m2.turnOn + + // m1.turnOn + // ^ error: State must be Off + // m2.turnOff + // ^ error: State must be On diff --git a/tests/run/erased-poly-ref.scala b/tests/run/erased-poly-ref.scala index 59badb71255d..975a576cc15b 100644 --- a/tests/run/erased-poly-ref.scala +++ b/tests/run/erased-poly-ref.scala @@ -8,10 +8,9 @@ object Test { def fun(erased a: Int): Unit = println("fun") - def foo[P](erased x: Int)(erased y: Int): Int = 0 + inline def foo[P](erased x: Int)(erased y: Int): Int = 0 - def bar(x: Int) = { - println(x) + inline def bar(x: Int) = { x } } diff --git a/tests/run/i11996.scala b/tests/run/i11996.scala index 9724e12b575e..a4318ace6c86 100644 --- a/tests/run/i11996.scala +++ b/tests/run/i11996.scala @@ -3,8 +3,8 @@ final class UnivEq[A] object UnivEq: - erased def force[A]: UnivEq[A] = - compiletime.erasedValue + inline def force[A]: UnivEq[A] = + caps.unsafe.unsafeErasedValue extension [A](a: A) inline def ==*[B >: A](b: B)(using erased UnivEq[B]): Boolean = a == b diff --git a/tests/run/i13691.scala b/tests/run/i13691.scala index 224656d87923..04f953d2da6b 100644 --- a/tests/run/i13691.scala +++ b/tests/run/i13691.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions -erased class CanThrow[-E <: Exception] -erased class Foo +class CanThrow[-E <: Exception] extends compiletime.Erased +class Foo extends compiletime.Erased class Bar object unsafeExceptions: diff --git a/tests/run/i16943.scala b/tests/run/i16943.scala index 68e1f8fb5aa3..697e9a2f38b7 100644 --- a/tests/run/i16943.scala +++ b/tests/run/i16943.scala @@ -1,6 +1,6 @@ @main @annotation.experimental -def Test(): Unit = fail(compiletime.erasedValue, 1) +def Test(): Unit = fail(caps.unsafe.unsafeErasedValue, 1) @annotation.experimental def fail(dumb: CanThrow[Exception], x: Int) = println(x) diff --git a/tests/run/i23305.scala b/tests/run/i23305.scala index 22cfe04339fc..862aed9d3362 100644 --- a/tests/run/i23305.scala +++ b/tests/run/i23305.scala @@ -1,6 +1,6 @@ //> using options -language:experimental.erasedDefinitions -erased trait DBMeta[A] +trait DBMeta[A] extends compiletime.Erased trait Table[A] diff --git a/tests/run/quotes-reflection/Test_2.scala b/tests/run/quotes-reflection/Test_2.scala index ce1cc8d3dff1..4ad0b17da9fa 100644 --- a/tests/run/quotes-reflection/Test_2.scala +++ b/tests/run/quotes-reflection/Test_2.scala @@ -1,6 +1,6 @@ import scala.language.experimental.erasedDefinitions -erased class EC +class EC extends compiletime.Erased trait X { def m1(using i: Int): Int diff --git a/tests/run/safeThrowsStrawman.scala b/tests/run/safeThrowsStrawman.scala index 973c9d8f5137..dc8da2f828c3 100644 --- a/tests/run/safeThrowsStrawman.scala +++ b/tests/run/safeThrowsStrawman.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions object scalax: - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased infix type raises[R, +E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/run/safeThrowsStrawman2.scala b/tests/run/safeThrowsStrawman2.scala index 1c84d84babc7..4de8f9bc7e2b 100644 --- a/tests/run/safeThrowsStrawman2.scala +++ b/tests/run/safeThrowsStrawman2.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions object scalax: - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased infix type raises[R, +E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 3f904b6bdda0..5f755e375ec3 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -2974,6 +2974,7 @@ Text => empty Language => Scala Symbols => 16 entries Occurrences => 12 entries +Diagnostics => 2 entries Symbols: example/NamedArguments# => class NamedArguments extends Object { self: NamedArguments => +4 decls } @@ -3007,6 +3008,10 @@ Occurrences: [5:7..5:12): apply -> example/NamedArguments#User.apply(). [5:13..5:17): name -> example/NamedArguments#User.apply().(name) +Diagnostics: +[4:2..4:21): [warning] A pure expression does nothing in statement position +[5:2..5:27): [warning] A pure expression does nothing in statement position + expect/NewModifiers.scala ------------------------- @@ -3654,7 +3659,7 @@ Text => empty Language => Scala Symbols => 62 entries Occurrences => 165 entries -Diagnostics => 3 entries +Diagnostics => 4 entries Synthetics => 39 entries Symbols: @@ -3890,6 +3895,7 @@ Occurrences: Diagnostics: [19:21..19:22): [warning] unused pattern variable +[28:4..28:9): [warning] A pure expression does nothing in statement position [41:4..41:5): [warning] unused pattern variable [63:10..63:11): [warning] unused explicit parameter