diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/SpecializedTraitsBenchmark.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/SpecializedTraitsBenchmark.scala new file mode 100644 index 000000000000..baf866c10e9a --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/SpecializedTraitsBenchmark.scala @@ -0,0 +1,100 @@ +// Run with: scala-cli --power --jmh bench-micro/src/main/scala/dotty/tools/benchmarks/SpecializedTraitsBenchmark.scala +// May have to run it again / delete .scala-build and rerun if you get a class not found +// error from scala-cli first time - the --jmh flag is still experimental. +// Don't forget to publish the compiler first and check that the version below corresponds to the generated version, +// as well as to kill the bloop server if you are republishing the same version as before. +// scala-cli --power bloop exit + +//> using scala 3.9.0-RC1-bin-SNAPSHOT-nonbootstrapped +//> using options -language:experimental.specializedTraits + +package dotty.tools.benchmarks + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit + +class VecManual(elems: Array[Int]): + private val num = summon[Numeric[Int]] + + def length = elems.length + + def apply(i: Int): Int = elems(i) + + def scalarProduct(other: VecManual): Int = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +class VecGeneric[T: Numeric](elems: Array[T]): + private val num = summon[Numeric[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: VecGeneric[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +inline trait VecSpecialized[T: {Specialized, Numeric2}](elems: Array[T]): + private val num = summon[Numeric2[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: VecSpecialized[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +@State(Scope.Benchmark) +class Arrays: + var arr1 = Array.fill(100_000_000) {math.round(math.random().floatValue * 4)} + var arr2 = Array.fill(100_000_000) {math.round(math.random().floatValue * 4)} + val target = arr1.zip(arr2).map((x, y) => x * y).fold(0)(_ + _) + +@State(Scope.Benchmark) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 15, time = 100, timeUnit = TimeUnit.MILLISECONDS) +@Fork(1) +class VecBench: + @Benchmark + def manual(arr: Arrays) = + val x = VecManual(arr.arr1) + val y = VecManual(arr.arr2) + assert(x.scalarProduct(y) == arr.target) + + @Benchmark + def generic(arr: Arrays) = + val x = VecGeneric[Int](arr.arr1) + val y = VecGeneric[Int](arr.arr2) + assert(x.scalarProduct(y) == arr.target) + + @Benchmark + def specialized(arr: Arrays) = + val x = new VecSpecialized[Int](arr.arr1) {} + val y = new VecSpecialized[Int](arr.arr2) {} + assert(x.scalarProduct(y) == arr.target) + +// You can really see the impact of Specialized on the interface usage here +// Remove Specialized and see that the generated code gets much more boxing and unboxing +// which slows it down substantially. +inline trait Numeric2[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +implicit object IntIsIntegral extends Numeric2[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index a47cbefee8da..4bfdfcc97933 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -43,6 +43,8 @@ class Compiler { List(new UnrollDefinitions) :: // Unroll annotated methods if detected in PostTyper List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only) List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols + List(new DesugarSpecializedTraits, // Process Specialized traits + new SpecializeInlineTraits) :: // Inline the code of inline traits into their children Nil /** Phases dealing with TASTY tree pickling and unpickling */ @@ -81,7 +83,9 @@ class Compiler { new ForwardDepChecks, // Check that there are no forward references to local vals new SpecializeApplyMethods, // Adds specialized methods to FunctionN new TryCatchPatterns, // Compile cases in try/catch - new PatternMatcher) :: // Compile pattern matches + new PatternMatcher, // Compile pattern matches + new PruneSpecializedMethods,// Remove specialized methods which have already been inlined + new PruneInlineTraits) :: // Remove right-hand side of definitions in inline traits List(new TestRecheck.Pre) :: // Test only: run rechecker, enabled under -Yrecheck-test List(new TestRecheck) :: // Test only: run rechecker, enabled under -Yrecheck-test List(new cc.Setup) :: // Preparations for check captures phase, enabled under captureChecking diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index dc8d9c700700..138130887937 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -98,7 +98,7 @@ class TreeTypeMap( override def transform(tree: Tree)(using Context): Tree = treeMap(tree) match { case impl @ Template(constr, _, self, _) => - val tmap = withMappedSyms(localSyms(impl :: self :: Nil)) + val tmap = withMappedSyms(localSyms(impl :: self :: Nil)) cpy.Template(impl)( constr = tmap.transformSub(constr), parents = impl.parents.mapconserve(transform), @@ -127,14 +127,15 @@ class TreeTypeMap( cpy.Block(blk)(stats1, expr1) case lit @ Literal(Constant(tpe: Type)) => cpy.Literal(lit)(Constant(mapType(tpe))) - case ddef @ DefDef(name, paramss, tpt, _) => + case ddef @ DefDef(name, paramss, tpt, _) => // Why are we not correctly mapping foo's return type? See Reached def def ... val (tmap1, paramss1) = transformAllParamss(paramss) val res = cpy.DefDef(ddef)(name, paramss1, tmap1.transform(tpt), tmap1.transform(ddef.rhs)) res.symbol.setParamssFromDefs(paramss1) res.symbol.transformAnnotations { case ann: BodyAnnotation => ann.derivedAnnotation(transform(ann.tree)) case ann => ann - } + } + // HERE? res case tdef @ LambdaTypeTree(tparams, body) => val (tmap1, tparams1) = transformDefs(tparams) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index de81a71f0bc9..b492043f520c 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -28,6 +28,7 @@ object Feature: val dependent = experimental("dependent") val erasedDefinitions = experimental("erasedDefinitions") + val specializedTraits = experimental("specializedTraits") val strictEqualityPatternMatching = experimental("strictEqualityPatternMatching") val symbolLiterals = deprecated("symbolLiterals") val saferExceptions = experimental("saferExceptions") @@ -67,6 +68,7 @@ object Feature: (scala2macros, "Allow Scala 2 macros"), (dependent, "Allow dependent method types"), (erasedDefinitions, "Allow erased definitions"), + (specializedTraits, "Allow specialized traits"), (strictEqualityPatternMatching, "relaxed CanEqual checks for ADT pattern matching"), (symbolLiterals, "Allow symbol literals"), (saferExceptions, "Enable safer exceptions"), diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 077056766a54..69cb434bde99 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -42,6 +42,8 @@ import plugins.* import java.nio.file.InvalidPathException import dotty.tools.dotc.coverage.Coverage import scala.annotation.tailrec +import dotty.tools.dotc.inlines.Inlines.InlineTraitState +import dotty.tools.dotc.transform.SpecializedTraitState object Contexts { @@ -146,6 +148,8 @@ object Contexts { def typerState: TyperState def gadt: GadtConstraint = gadtState.gadt def gadtState: GadtState + def inlineTraitState: InlineTraitState + def specializedTraitState: SpecializedTraitState def searchHistory: SearchHistory def source: SourceFile @@ -436,6 +440,8 @@ object Contexts { superOrThisCallContext(owner, constrCtx.scope) .setTyperState(typerState) .setGadtState(gadtState) + .setInlineTraitState(inlineTraitState) + .setSpecializedTraitState(specializedTraitState) .fresh .setScope(this.scope) } @@ -603,6 +609,12 @@ object Contexts { private var _gadtState: GadtState = uninitialized final def gadtState: GadtState = _gadtState + + private var _inlineTraitState: InlineTraitState = uninitialized + final def inlineTraitState: InlineTraitState = _inlineTraitState + + private var _specializedTraitState: SpecializedTraitState = uninitialized + final def specializedTraitState: SpecializedTraitState = _specializedTraitState private var _searchHistory: SearchHistory = uninitialized final def searchHistory: SearchHistory = _searchHistory @@ -628,6 +640,8 @@ object Contexts { _tree = origin.tree _scope = origin.scope _gadtState = origin.gadtState + _inlineTraitState = origin.inlineTraitState + _specializedTraitState = origin.specializedTraitState _searchHistory = origin.searchHistory _source = origin.source _moreProperties = origin.moreProperties @@ -691,6 +705,16 @@ object Contexts { def setFreshGADTBounds: this.type = setGadtState(gadtState.fresh) + def setInlineTraitState(inlineTraitState: InlineTraitState): this.type = + util.Stats.record("Context.setInlineTraitState") + this._inlineTraitState = inlineTraitState + this + + def setSpecializedTraitState(specializedTraitState: SpecializedTraitState): this.type = + util.Stats.record("Context.setSpecializedTraitState") + this._specializedTraitState = specializedTraitState + this + def setSearchHistory(searchHistory: SearchHistory): this.type = util.Stats.record("Context.setSearchHistory") this._searchHistory = searchHistory @@ -785,6 +809,8 @@ object Contexts { .updated(profilerLoc, Profiler.NoOp) c._searchHistory = new SearchRoot c._gadtState = GadtState(GadtConstraint.empty) + c._inlineTraitState = InlineTraitState() + c._specializedTraitState = SpecializedTraitState() c end FreshContext diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 152b9a55cde4..54ff52e02f78 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -236,6 +236,7 @@ class Definitions { @tu lazy val ScalaCollectionImmutablePackageClass: ClassSymbol = requiredPackage("scala.collection.immutable").moduleClass.asClass @tu lazy val ScalaMathPackageClass: ClassSymbol = requiredPackage("scala.math").moduleClass.asClass @tu lazy val ScalaUtilPackageClass: ClassSymbol = requiredPackage("scala.util").moduleClass.asClass + @tu lazy val ScalaSpecializePackageVal: TermSymbol = requiredPackage("scala.specialize") // fundamental modules @tu lazy val SysPackage : Symbol = requiredModule("scala.sys.package") @@ -793,6 +794,10 @@ class Definitions { @tu lazy val StringAddClass : ClassSymbol = requiredClass("scala.runtime.StringAdd") @tu lazy val StringAdd_+ : Symbol = StringAddClass.requiredMethod(nme.raw.PLUS) + @tu lazy val SpecializedClass : ClassSymbol = requiredClass("scala.specialize.Specialized") + @tu lazy val SpecializedModule: Symbol = SpecializedClass.companionModule + @tu lazy val SpecializedModule_apply: Symbol = SpecializedModule.requiredMethod(nme.apply) + @tu lazy val StringContextClass: ClassSymbol = requiredClass("scala.StringContext") @tu lazy val StringContext_s : Symbol = StringContextClass.requiredMethod(nme.s) @tu lazy val StringContext_raw: Symbol = StringContextClass.requiredMethod(nme.raw_) @@ -1734,6 +1739,9 @@ class Definitions { private val PredefImportFns: RootRef = RootRef(() => ScalaPredefModule.termRef) + private val SpecializeImportFns: RootRef = // TODO: Find a solution to importing in scala package without conflict with original specialized. + RootRef(() => ScalaSpecializePackageVal.termRef) + @tu private lazy val YimportsImportFns: List[RootRef] = ctx.settings.Yimports.value.map { name => val denot = getModuleIfDefined(name).suchThat(_.is(Module)) `orElse` @@ -1749,8 +1757,8 @@ class Definitions { @tu private lazy val ScalaRootImportFns: List[RootRef] = if !ctx.settings.Yimports.isDefault then YimportsImportFns else if ctx.settings.YnoImports.value then Nil - else if ctx.settings.YnoPredef.value then ScalaImportFns - else ScalaImportFns :+ PredefImportFns + else if ctx.settings.YnoPredef.value then ScalaImportFns :+ SpecializeImportFns + else ScalaImportFns :+ SpecializeImportFns :+ PredefImportFns @tu private lazy val JavaRootImportTypes: List[TermRef] = JavaRootImportFns.map(_.refFn()) @tu private lazy val ScalaRootImportTypes: List[TermRef] = ScalaRootImportFns.map(_.refFn()) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index af7455fa2994..3085db1a0043 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -453,13 +453,13 @@ object Flags { /** Flags representing source modifiers */ private val CommonSourceModifierFlags: FlagSet = - commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased) + commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased, Inline) val TypeSourceModifierFlags: FlagSet = CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open | Into val TermSourceModifierFlags: FlagSet = - CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked + CommonSourceModifierFlags.toTermFlags | AbsOverride | Lazy | Tracked /** Flags representing modifiers that can appear in trees */ val ModifierFlags: FlagSet = @@ -592,6 +592,7 @@ object Flags { val InlineOrProxy: FlagSet = Inline | InlineProxy // An inline method or inline argument proxy */ val InlineMethod: FlagSet = Inline | Method val InlineImplicitMethod: FlagSet = Implicit | InlineMethod + val InlineTrait: FlagSet = Inline | Trait val InlineParam: FlagSet = Inline | Param val InlineByNameProxy: FlagSet = InlineProxy | Method val JavaEnum: FlagSet = JavaDefined | Enum // A Java enum trait diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index a433ed4375c6..a3375c2eed8e 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -77,6 +77,9 @@ object NameOps { def isAnonymousFunctionName: Boolean = name.startsWith(str.ANON_FUN) def isUnapplyName: Boolean = name == nme.unapply || name == nme.unapplySeq def isRightAssocOperatorName: Boolean = name.lastPart.last == ':' + def isSpecializedTraitInterfaceName: Boolean = name.toString.contains(str.SPECIALIZED_TRAIT_SUFFIX) + def isSpecializedTraitImplementationName: Boolean = name.toString.contains(str.SPECIALIZED_TRAIT_IMPL_SUFFIX) + def isRawSpecializedTraitImplementationName: Boolean = name.toString.endsWith(str.SPECIALIZED_TRAIT_IMPL_SUFFIX) /** Does this name match `[{letter | digit} '_'] op`? * diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 4a08351680ec..7314fd5dee79 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -234,6 +234,8 @@ object Phases { private var mySbtExtractAPIPhase: Phase = uninitialized private var myPicklerPhase: Phase = uninitialized private var mySetRootTreePhase: Phase = uninitialized + private var mySpecializeInlineTraitsPhase: Phase = uninitialized + private var myDesugarSpecializedTraitsPhase: Phase = uninitialized private var myInliningPhase: Phase = uninitialized private var myStagingPhase: Phase = uninitialized private var mySplicingPhase: Phase = uninitialized @@ -266,6 +268,8 @@ object Phases { final def sbtExtractAPIPhase: Phase = mySbtExtractAPIPhase final def picklerPhase: Phase = myPicklerPhase final def setRootTreePhase: Phase = mySetRootTreePhase + final def specializeInlineTraitsPhase: Phase = mySpecializeInlineTraitsPhase + final def desugarSpecializedTraitsPhase: Phase = myDesugarSpecializedTraitsPhase final def inliningPhase: Phase = myInliningPhase final def stagingPhase: Phase = myStagingPhase final def splicingPhase: Phase = mySplicingPhase @@ -298,6 +302,8 @@ object Phases { mySbtExtractAPIPhase = phaseOfClass(classOf[sbt.ExtractAPI]) mySetRootTreePhase = phaseOfClass(classOf[SetRootTree]) myPicklerPhase = phaseOfClass(classOf[Pickler]) + mySpecializeInlineTraitsPhase = phaseOfClass(classOf[SpecializeInlineTraits]) + myDesugarSpecializedTraitsPhase = phaseOfClass(classOf[DesugarSpecializedTraits]) myInliningPhase = phaseOfClass(classOf[Inlining]) myStagingPhase = phaseOfClass(classOf[Staging]) mySplicingPhase = phaseOfClass(classOf[Splicing]) @@ -560,6 +566,8 @@ object Phases { def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def sbtExtractAPIPhase(using Context): Phase = ctx.base.sbtExtractAPIPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase + def specializeInlineTraitsPhase(using Context): Phase = ctx.base.specializeInlineTraitsPhase + def desugarSpecializedTraitsPhase(using Context): Phase = ctx.base.desugarSpecializedTraitsPhase def inliningPhase(using Context): Phase = ctx.base.inliningPhase def stagingPhase(using Context): Phase = ctx.base.stagingPhase def splicingPhase(using Context): Phase = ctx.base.splicingPhase diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 205e649b2390..c1a034ef8dc3 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -26,6 +26,11 @@ object StdNames { inline val LOCALDUMMY_PREFIX = " rinfo match @@ -646,6 +646,20 @@ object SymDenotations { final def isAnonymousClass(using Context): Boolean = isClass && initial.name.isAnonymousClassName + /** Is this symbol an specialized trait interface? */ + final def isSpecializedTraitInterface(using Context): Boolean = + isClass && name.isSpecializedTraitInterfaceName + + /** Is this symbol an specialized trait implementation class? */ + final def isSpecializedTraitImplementationClass(using Context): Boolean = + isClass && name.isSpecializedTraitImplementationName + + /** Is this symbol a specialized trait implementation class that + * was generated from a specialization using only top classes / Nothing + * and is therefore not subject to a specialized interface */ + final def isRawSpecializedTraitImplementationClass(using Context): Boolean = + isClass && name.isSpecializedTraitImplementationName + final def isAnonymousFunction(using Context): Boolean = this.symbol.is(Method) && initial.name.isAnonymousFunctionName @@ -1050,6 +1064,15 @@ object SymDenotations { def isInlineMethod(using Context): Boolean = isAllOf(InlineMethod, butNot = Accessor) + def isInlineTrait(using Context): Boolean = + isAllOf(InlineTrait) + + def isSpecializedMethod(using Context): Boolean = + Specialization.isSpecializedMethod(symbol) + + def isSpecializedTrait(using Context): Boolean = + Specialization.isSpecializedTrait(symbol) + /** Does this method or field need to be retained at runtime */ def isRetainedInline(using Context): Boolean = is(Inline, butNot = Deferred) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index ab2c273ecaed..ca2a2c3f70b6 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -977,6 +977,17 @@ object Symbols extends SymUtils { copies.foreach(_.ensureCompleted()) // avoid memory leak + // We add this because without it retainsDefTree is not consistent with defTree. + // Also, we need it to get the correct source files when unpickling coords inlined + // from inline traits. This does change the output of tests/run-macros/tasty-extractors-owners + // as the tree is now present to be returned from the macro so some Nones turn into Somes + // and a few Inferred() turn into Apply(Select(New(Inferred()), ""), Nil). + copies.zip(originals).foreach { (copied, original) => + if copied.retainsDefTree then + copied.defTree = original.defTree + } + + // Update Child annotations of classes encountered previously to new values // if some child is among the mapped symbols for orig <- ttmap1.substFrom do diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 661ca9f75f01..5617dca3a386 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -26,7 +26,10 @@ import Capabilities.Capability import NameKinds.WildcardParamName import MatchTypes.isConcrete import reporting.Message.Note +import reporting.IllegalVarianceInSpecializedTraitsNote import scala.util.boundary, boundary.break +import dotty.tools.dotc.transform.Specialization +import dotty.tools.dotc.transform.DesugarSpecializedTraits /** Provides methods to compare types. */ @@ -160,7 +163,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def testSubType(tp1: Type, tp2: Type): CompareResult = GADTused = false opaquesUsed = false - if !topLevelSubType(tp1, tp2) then CompareResult.Fail(Nil) + errorNotes = Nil + if !topLevelSubType(tp1, tp2) then CompareResult.Fail(errorNotes.map(_._2)) else if GADTused then CompareResult.OKwithGADTUsed else if opaquesUsed then CompareResult.OKwithOpaquesUsed // we cast on GADTused, so handles if both are used else CompareResult.OK @@ -1935,8 +1939,29 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && defn.isByNameFunction(arg2.dealias) => isSubArg(arg1res, arg2.argInfos.head) case _ => - if v < 0 then isSubType(arg2, arg1) - else if v > 0 then isSubType(arg1, arg2) + if v < 0 then + val isValidSubtype = isSubType(arg2, arg1) + if tp1.classSymbol.isSpecializedTrait + && Specialization.traitParamIsSpecialized(tp1.classSymbol, tparam.paramRef.typeSymbol) + && isValidSubtype + && !(DesugarSpecializedTraits.isSameErasureBucket(arg1, arg2)) + then // using contravariance in a way which specialized trait erasure cannot support + addErrorNote(IllegalVarianceInSpecializedTraitsNote()) + false + else + isValidSubtype + else if v > 0 then + val isValidSubtype = isSubType(arg1, arg2) + if tp1.classSymbol.isSpecializedTrait + && Specialization.traitParamIsSpecialized(tp1.classSymbol, tparam.paramRef.typeSymbol) + && isValidSubtype + && (arg1 ne arg2) + && (arg1.classSymbol == defn.NothingClass) + then + addErrorNote(IllegalVarianceInSpecializedTraitsNote()) + false + else + isValidSubtype else isSameType(arg2, arg1) val arg1 = args1.head diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 8e0539e98fdd..f1f3cfb881bb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -7,12 +7,15 @@ import Flags.JavaDefined import Uniques.unique import backend.sjs.JSDefinitions import transform.ExplicitOuter.* +import transform.Specialization import transform.ValueClasses.* import transform.ContextFunctionResults.* import unpickleScala2.Scala2Erasure import Decorators.* import Definitions.MaxImplementedFunctionArity import scala.annotation.tailrec +import dotty.tools.dotc.transform.DesugarSpecializedTraits +import dotty.tools.dotc.util.Property /** The language in which the definition being erased was written. */ enum SourceLanguage: @@ -76,8 +79,10 @@ end SourceLanguage */ object TypeErasure: + private val DisallowSpecialized = Property.Key[Unit] + private def erasureDependsOnArgs(sym: Symbol)(using Context) = - sym == defn.ArrayClass || sym == defn.PairClass || sym.isDerivedValueClass + sym == defn.ArrayClass || sym == defn.PairClass || sym.isDerivedValueClass || sym.isSpecializedTrait /** The arity of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs. * @@ -206,6 +211,14 @@ object TypeErasure: def preErasureCtx(using Context) = if (ctx.erasedTypes) ctx.withPhase(erasurePhase) else ctx + /** The current context but with Foo[Int] erasing to Foo instead of + * Foo$sp$Int when Foo is a specialized trait. */ + def disallowSpecializedCtx(using Context) = ctx.fresh.setProperty(DisallowSpecialized, ()) + + /** The current context but with Foo[Int] erasing to Foo$sp$Int instead of + * Foo when Foo is a specialized trait. */ + def allowSpecializedCtx(using Context) = ctx.fresh.dropProperty(DisallowSpecialized) + /** The standard erasure of a Scala type. Value classes are erased as normal classes. * * @param tp The type to erase. @@ -776,6 +789,15 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst else if semiEraseVCs && sym.isDerivedValueClass then eraseDerivedValueClass(tp) else if defn.isSyntheticFunctionClass(sym) then defn.functionTypeErasure(sym) else eraseNormalClassRef(tp) + case Specialization(spec) if ((ctx.phase == erasurePhase || ctx.erasedTypes) // At the beginning the $sp$ trait symbols are not present so up until + // erasure need to consider the signature of def foo(x: Foo[Int]): Int as + // foo(Foo):Int. Only at erasure do the symbol swap. This ensures + // the signatures don't change before erasure. + && spec.isSpecialized && ctx.property(DisallowSpecialized).isEmpty) => + val specName = DesugarSpecializedTraits.newSpecializedTraitName(spec) // TODO: Maybe better as method on spec + val interfaceSymbol = spec.traitSymbol.owner.enclosingPackageClass.info.decls.lookup(specName) + assert(interfaceSymbol.exists && interfaceSymbol.isClass) + this(interfaceSymbol.typeRef.appliedTo(spec.unspecializedTypeArgs.map(_.tpe))) case tp: AppliedType => val tycon = tp.tycon if (tycon.isRef(defn.ArrayClass)) eraseArray(tp) @@ -866,19 +888,36 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst case tp @ ClassInfo(pre, cls, parents, decls, _) => if (cls.is(Package)) tp else { - def eraseParent(tp: Type) = tp.dealias match { // note: can't be opaque, since it's a class parent + def eraseParent(tp: Type)(using Context) = tp.dealias match { // note: can't be opaque, since it's a class parent case tp: AppliedType if tp.tycon.isRef(defn.PairClass) => defn.ObjectType case _ => apply(tp) } val erasedParents: List[Type] = if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil - else parents.mapConserve(eraseParent) match { - case tr :: trs1 => - assert(!tr.classSymbol.is(Trait), i"$cls has bad parents $parents%, %") - val tr1 = if (cls.is(Trait)) defn.ObjectType else tr - tr1 :: trs1.filterNot(_.isAnyRef) - case nil => nil - } + else + // Match corresponding tree erasure in Erasure::typedClassDef + val parents1 = + if cls.isSpecializedTraitInterface then // {source: Bar, Foo both specialized traits} inline trait Bar$sp$Int extends Object, Bar, Foo$sp$Int + val (obj :: originalTrait :: inheritedParents) = parents : @unchecked + eraseParent(obj) :: apply(originalTrait)(using disallowSpecializedCtx) :: inheritedParents.mapConserve(eraseParent(_)(using allowSpecializedCtx)) + else if cls.isSpecializedTraitImplementationClass && !cls.isRawSpecializedTraitImplementationClass then // {source: Bar, Foo both specialized traits} class Bar$impl$Int extends Object, Bar$sp$Int, Bar(10) + val (objectParent :: traitSpParent :: originalTraitSpecializedParent :: Nil) = parents : @unchecked + eraseParent(objectParent) :: eraseParent(traitSpParent)(using allowSpecializedCtx) :: apply(originalTraitSpecializedParent)(using disallowSpecializedCtx) :: Nil + else + val originalSpecializedTraits = parents.filter(p => p.typeSymbol.isSpecializedTrait).map(eraseParent(_)(using allowSpecializedCtx)) + + // {source: class Bar extends Foo[Int](10) with Baz[Int](10)} + // class Bar extends Object, Foo(10), Bar(10), Foo$sp$Int, Bar$sp$Int + parents.mapConserve(p => if p.typeSymbol.isSpecializedTrait then + apply(p)(using disallowSpecializedCtx) + else eraseParent(p)) ::: originalSpecializedTraits + parents1 match { + case tr :: trs1 => + assert(!tr.classSymbol.is(Trait), i"$cls has bad parents $parents%, %") + val tr1 = if (cls.is(Trait)) defn.ObjectType else tr + tr1 :: trs1.filterNot(_.isAnyRef) + case nil => nil + } val erasedDecls = decls.filteredScope( keep = sym => !sym.isType || sym.isClass, rename = sym => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index dec3d8b63c17..94678c552a00 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1355,6 +1355,7 @@ object Types extends TypeUtils { case _ => this } + /** Strip PolyType prefixes */ def stripPoly(using Context): Type = this match { case tp: PolyType => tp.resType.stripPoly diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 258b9f7c06a9..b71a6199e6ad 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -31,6 +31,7 @@ import Trees.* import Decorators.* import config.Feature import quoted.QuotePatterns +import inlines.Inlines import dotty.tools.tasty.{TastyBuffer, TastyReader} import TastyBuffer.* @@ -1171,14 +1172,34 @@ class TreeUnpickler(reader: TastyReader, .map(_.changeOwner(localDummy, constr.symbol))) else parents + val statsStart = currentAddr val lazyStats = readLater(end, rdr => { val stats = rdr.readIndexedStats(localDummy, end) tparams ++ vparams ++ stats }) + NamerOps.addConstructorProxies(cls) NamerOps.addContextBoundCompanions(cls) + + // Because opaque types can appear in inline traits and these are only allowed to be completed once (otherwise cyclic reference error) + // we need to force the body stats now if we have an inline trait so that we don't complete them twice, once in the LazyBodyAnnot and once + // in the main code. + val strictOrLazyStats = + if cls.isInlineTrait then + val strictStats = lazyStats.complete + cls.addAnnotation(LazyBodyAnnotation { (ctx0: Context) ?=> + val ctx1 = localContext(cls)(using ctx0).addMode(Mode.ReadPositions) + inContext(sourceChangeContext(Addr(0))(using ctx1)) { + // avoids space leaks by not capturing the current context + val inlinedMembers = strictStats.filter(member => Inlines.isInlineableFromInlineTrait(cls, member)) + Block(inlinedMembers, unitLiteral).withSpan(cls.span) + } + }) + strictStats + else + lazyStats setSpan(start, - untpd.Template(constr, mappedParents, self, lazyStats) + untpd.Template(constr, mappedParents, self, strictOrLazyStats) .withType(localDummy.termRef)) } diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 4eee4884f954..2134f9240e9b 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -119,9 +119,10 @@ object Inliner: oldOwners: List[Symbol], newOwners: List[Symbol], substFrom: List[Symbol], - substTo: List[Symbol])(using Context) + substTo: List[Symbol], + val inlineCopier: TreeCopier)(using Context) extends TreeTypeMap( - typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, InlineCopier()): + typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, inlineCopier): override def transform(tree: Tree)(using Context): Tree = tree match @@ -147,7 +148,7 @@ object Inliner: newOwners: List[Symbol], substFrom: List[Symbol], substTo: List[Symbol])(using Context) = - new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) + new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, inlineCopier) override def transformInlined(tree: Inlined)(using Context) = if tree.inlinedFromOuterScope then @@ -337,7 +338,7 @@ class Inliner(val call: tpd.Tree)(using Context): private def classNestingLevel(cls: Symbol) = cls.ownersIterator.count(_.isClass) // Compute val-definitions for all this-proxies and append them to `bindingsBuf` - private def computeThisBindings() = { + protected def computeThisBindings() = { // All needed this-proxies, paired-with and sorted-by nesting depth of // the classes they represent (innermost first) val sortedProxies = thisProxy.toList @@ -504,7 +505,7 @@ class Inliner(val call: tpd.Tree)(using Context): else arg else arg - private def canElideThis(tpe: ThisType): Boolean = + protected def canElideThis(tpe: ThisType): Boolean = inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || tpe.cls.isContainedIn(inlinedMethod) || tpe.cls.is(Package) @@ -553,7 +554,6 @@ class Inliner(val call: tpd.Tree)(using Context): if thisTypeProxyExists then mapBackToOpaques.typeMap(thisTypeUnpacker.typeMap(inlined.expansion.tpe)) else inlined.tpe - /** Populate `thisProxy` and `paramProxy` as follows: * * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, @@ -609,7 +609,7 @@ class Inliner(val call: tpd.Tree)(using Context): * from its `originalOwner`, and, if it comes from outside the inlined method * itself, it has to be marked as an inlined argument. */ - private def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = + protected def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = // assertAllPositioned(tree) // debug tree.changeOwner(originalOwner, ctx.owner) @@ -621,9 +621,73 @@ class Inliner(val call: tpd.Tree)(using Context): val reducer = new InlineReducer(this) - /** The Inlined node representing the inlined call */ - def inlined(rhsToInline: tpd.Tree): (List[MemberDef], Tree) = + protected class InlinerTypeMap extends DeepTypeMap { + override def stopAt = + if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package + def apply(t: Type) = t match { + case t: ThisType => thisProxy.get(t.cls).getOrElse(t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => + if t.termSymbol.isAllOf(InlineParam) then apply(t.widenTermRefExpr) + else paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + } + + protected class InlinerTreeMap extends (Tree => Tree) { + def apply(tree: Tree) = tree match { + case tree: This => + tree.tpe match { + case thistpe: ThisType => + thisProxy.get(thistpe.cls) match { + case Some(t) => + val thisRef = ref(t).withSpan(call.span) + inlinedFromOutside(thisRef)(tree.span) + case None => tree + } + case _ => tree + } + case tree: Ident => + /* Span of the argument. Used when the argument is inlined directly without a binding */ + def argSpan = + if (tree.name == nme.WILDCARD) tree.span // From type match + else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? + else paramSpan(tree.name) + val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) + paramProxy.get(tree.tpe) match { + case Some(t) if tree.isTerm && t.isSingleton => + val inlinedSingleton = singleton(t).withSpan(argSpan) + inlinedFromOutside(inlinedSingleton)(tree.span) + case Some(t) if tree.isType => + inlinedFromOutside(new InferredTypeTree().withType(t).withSpan(argSpan))(tree.span) + case _ => tree + } + case tree @ Select(qual: This, name) if tree.symbol.is(Private) && tree.symbol.isInlineMethod => + // This inline method refers to another (private) inline method (see tests/pos/i14042.scala). + // We insert upcast to access the private inline method once inlined. This makes the selection + // keep the symbol when re-typechecking in the InlineTyper. The method is inlined and hence no + // reference to a private method is kept at runtime. + cpy.Select(tree)(qual.asInstance(qual.tpe.widen), name) + + case tree => tree + } + + private def inlinedFromOutside(tree: Tree)(span: Span): Tree = + Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) + } + + protected val inlinerTypeMap: InlinerTypeMap = InlinerTypeMap() + protected val inlinerTreeMap: InlinerTreeMap = InlinerTreeMap() + protected def substFrom: List[Symbol] = Nil + protected def substTo: List[Symbol] = Nil + protected def inlineCopier: TreeCopier = InlineCopier() + + protected def inlineCtx(inlineTyper: InlineTyper)(using Context): Context = + inlineContext(Inlined(call, Nil, ref(defn.Predef_undefined))).fresh.setTyper(inlineTyper).setNewScope + + /** The Inlined node representing the inlined call */ + def inlined(rhsToInline: tpd.Tree)(using Context): (List[MemberDef], Tree) = inlining.println(i"-----------------------\nInlining $call\nWith RHS $rhsToInline") def paramTypess(call: Tree, acc: List[List[Type]]): List[List[Type]] = call match @@ -665,69 +729,26 @@ class Inliner(val call: tpd.Tree)(using Context): val inlineTyper = new InlineTyper(ctx.reporter.errorCount) - val inlineCtx = inlineContext(Inlined(call, Nil, ref(defn.Predef_undefined))).fresh.setTyper(inlineTyper).setNewScope - - def inlinedFromOutside(tree: Tree)(span: Span): Tree = - Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) + val inlineCtx = this.inlineCtx(inlineTyper) // A tree type map to prepare the inlined body for typechecked. // The translation maps references to `this` and parameters to // corresponding arguments or proxies on the type and term level. It also changes // the owner from the inlined method to the current owner. - val inliner = new InlinerMap( - typeMap = - new DeepTypeMap { - override def stopAt = - if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package - def apply(t: Type) = t match { - case t: ThisType => thisProxy.getOrElse(t.cls, t) - case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) - case t: SingletonType => - if t.termSymbol.isAllOf(InlineParam) then apply(t.widenTermRefExpr) - else paramProxy.getOrElse(t, mapOver(t)) - case t => mapOver(t) - } - }, - treeMap = { - case tree: This => - tree.tpe match { - case thistpe: ThisType => - thisProxy.get(thistpe.cls) match { - case Some(t) => - val thisRef = ref(t).withSpan(call.span) - inlinedFromOutside(thisRef)(tree.span) - case None => tree - } - case _ => tree - } - case tree: Ident => - /* Span of the argument. Used when the argument is inlined directly without a binding */ - def argSpan = - if (tree.name == nme.WILDCARD) tree.span // From type match - else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? - else paramSpan(tree.name) - val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) - paramProxy.get(tree.tpe) match { - case Some(t) if tree.isTerm && t.isSingleton => - val inlinedSingleton = singleton(t).withSpan(argSpan) - inlinedFromOutside(inlinedSingleton)(tree.span) - case Some(t) if tree.isType => - inlinedFromOutside(new InferredTypeTree().withType(t).withSpan(argSpan))(tree.span) - case _ => tree - } - case tree @ Select(qual: This, name) if tree.symbol.is(Private) && tree.symbol.isInlineMethod => - // This inline method refers to another (private) inline method (see tests/pos/i14042.scala). - // We insert upcast to access the private inline method once inlined. This makes the selection - // keep the symbol when re-typechecking in the InlineTyper. The method is inlined and hence no - // reference to a private method is kept at runtime. - cpy.Select(tree)(qual.asInstance(qual.tpe.widen), name) - case tree => tree - }, - oldOwners = inlinedMethod :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = Nil, - substTo = Nil + // TODO: This gets around the fact that inline traits doesn't define inlinedMethod correctly but maybe there is a better + // way. + val oldOwners = if (inlinedMethod.exists) then inlinedMethod :: Nil else Nil + val newOwners = if (inlinedMethod.exists) then ctx.owner :: Nil else Nil + + val inliner = new InlinerMap( + typeMap = inlinerTypeMap, + treeMap = inlinerTreeMap, + oldOwners = oldOwners, + newOwners = newOwners, + substFrom = substFrom, + substTo = substTo, + inlineCopier = inlineCopier )(using inlineCtx) inlining.println( @@ -808,7 +829,6 @@ class Inliner(val call: tpd.Tree)(using Context): if (inlinedMethod == defn.Compiletime_error) issueError() addInlinedTrees(treeSize(finalExpansion)) - (finalBindings, finalExpansion) } end inlined diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 859648a47c6a..2e4c7116c423 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -3,8 +3,9 @@ package dotc package inlines import ast.*, core.* -import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.* -import StdNames.{tpnme, nme} +import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.*, TypeOps.* +import Names.Name +import StdNames.{str, tpnme, nme} import NameOps.* import typer.* import NameKinds.BodyRetainerName @@ -22,9 +23,20 @@ import cc.CleanupRetains import collection.mutable import reporting.{NotConstant, trace} -import util.Spans.Span +import util.Spans.{Span, spanCoord} import dotty.tools.dotc.core.Periods.PhaseId import dotty.tools.dotc.util.chaining.* +import NameOps.expandedName +import dotty.tools.dotc.core.Annotations.ConcreteBodyAnnotation +import dotty.tools.dotc.core.Annotations.LazyBodyAnnotation +import dotty.tools.dotc.core.Scopes.EmptyScope +import dotty.tools.dotc.core.Scopes.MutableScope +import dotty.tools.dotc.reporting.OverrideError +import dotty.tools.dotc.typer.RefChecks.OverridingPairsChecker +import dotty.tools.dotc.typer.ErrorReporting.err +import dotty.tools.dotc.core.NameKinds.DefaultGetterName +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.inlines.Inlines.InlineTraitState.InlineContext /** Support for querying inlineable methods and for inlining calls to such methods */ object Inlines: @@ -38,7 +50,7 @@ object Inlines: /** `sym` is an inline method with a known body to inline. */ def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = - sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) + (sym.isInlineMethod || sym.isInlineTrait) && sym.hasAnnotation(defn.BodyAnnot) /** The body to inline for method `sym`, or `EmptyTree` if none exists. * @pre hasBodyToInline(sym) @@ -54,39 +66,94 @@ object Inlines: else EmptyTree + def defsToInline(traitSym: SymDenotation)(using Context): List[Tree] = + bodyToInline(traitSym) match + case Block(defs, _) if traitSym.isInlineTrait => defs + case _ => Nil + /** Are we in an inline method body? */ def inInlineMethod(using Context): Boolean = ctx.owner.ownersIterator.exists(_.isInlineMethod) + def inInlineContext(using Context): Boolean = + ctx.owner.ownersIterator.exists(sym => sym.isInlineMethod || sym.isInlineTrait) + /** Can a call to method `meth` be inlined? */ def isInlineable(meth: Symbol)(using Context): Boolean = - meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod + meth.isInlineMethod && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod + + def isInlineableFromInlineTrait(inlinedTraitSym: ClassSymbol, member: tpd.Tree)(using Context): Boolean = + !(member.isInstanceOf[tpd.TypeDef] && inlinedTraitSym.typeParams.contains(member.symbol)) + && !member.symbol.isAllOf(Inline) + // && !member.symbol.is(Deferred) // Also inline interfaces (see specialized-trait-collections-example.scala) /** Should call be inlined in this context? */ - def needsInlining(tree: Tree)(using Context): Boolean = tree match { - case Block(_, expr) => needsInlining(expr) - case _ => - def isUnapplyExpressionWithDummy: Boolean = - // The first step of typing an `unapply` consists in typing the call - // with a dummy argument (see Applications.typedUnApply). We delay the - // inlining of this call. - def rec(tree: Tree): Boolean = tree match - case Apply(_, ProtoTypes.dummyTreeOfType(_) :: Nil) => true - case Apply(fn, _) => rec(fn) - case _ => false - tree.symbol.name.isUnapplyName && rec(tree) - - isInlineable(tree.symbol) - && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] - && StagingLevel.level == 0 + def needsInlining(tree: Tree)(using Context): Boolean = + def isInlineableInCtx = + StagingLevel.level == 0 && ( ctx.phase == Phases.inliningPhase || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) + || (ctx.inlineTraitState.inlineTraitsPhase == InlineTraitState.InlineContext.InlineTraits && !tree.symbol.is(Macro) && !(tree.symbol.isSpecializedTraitImplementationClass || tree.symbol.isSpecializedTraitInterface)) + || (ctx.inlineTraitState.inlineTraitsPhase == InlineTraitState.InlineContext.SpecializedTraits && !tree.symbol.is(Macro) && (tree.symbol.isSpecializedTraitImplementationClass || tree.symbol.isSpecializedTraitInterface) ) ) && !ctx.typer.hasInliningErrors && !ctx.base.stopInlining - && !ctx.mode.is(Mode.NoInline) - && !isUnapplyExpressionWithDummy + // && !ctx.owner.ownersIterator.exists(_.isInlineTrait) + + tree match + case Block(_, expr) => + needsInlining(expr) + case tdef @ TypeDef(_, impl: Template) => + impl.parents.map(symbolFromParent).exists(sym => sym.isInlineTrait) && isInlineableInCtx // We also allow inlining into inline traits for consistency when implementing specialized traits (see docs) + case _ => + def isUnapplyExpressionWithDummy: Boolean = + // The first step of typing an `unapply` consists in typing the call + // with a dummy argument (see Applications.typedUnApply). We delay the + // inlining of this call. + def rec(tree: Tree): Boolean = tree match + case Apply(_, ProtoTypes.dummyTreeOfType(_) :: Nil) => true + case Apply(fn, _) => rec(fn) + case _ => false + tree.symbol.name.isUnapplyName && rec(tree) + // We don't inline inline calls into inline traits because we can inline them into the children and this gets around the issue of + // needing to apply the symbol replacement map to those calls (which is tricky because we don't apply any inline maps beyond Inlined() calls) + // See tests/pos/i11866.scala and tests/run/specialized-trait-vector-dot-product.scala + isInlineable(tree.symbol) && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] && isInlineableInCtx && !(ctx.owner.ownersIterator.exists(_.isInlineTrait)) && !ctx.mode.is(Mode.NoInline) && !isUnapplyExpressionWithDummy + + private[dotc] def symbolFromParent(parent: Tree)(using Context): Symbol = + if parent.symbol.isConstructor then parent.symbol.owner else parent.tpe.typeSymbol + + // Inline trait ancestors in linearization order. + private def inlineTraitAncestors(cls: TypeDef)(using Context): List[Tree] = cls match { + case tpd.TypeDef(_, tmpl: Template) => + val parentTrees: Map[Symbol, Tree] = tmpl.parents.map(par => symbolFromParent(par) -> par).toMap.filter(_._1.isInlineTrait) + val ancestors: List[ClassSymbol] = cls.tpe.baseClasses.filter(sym => sym != cls.symbol && sym.isInlineTrait // && // TODO: Do we not need to stop if there is a non-inline trait somewhere in the hierarchy? It should block the inlining right? + && !(cls.symbol.asClass.ownersIterator.toList.tail.exists(p => p.isInlineTrait)) // We can skip anything that would be inlined nested into an inline trait because it must be pruned out later + ) + ancestors.flatMap(ancestor => + def baseTree = + cls.tpe.baseType(ancestor) match + case AppliedType(tycon, targs) => + Some(AppliedTypeTree(TypeTree(tycon), targs.map(TypeTree(_)))) + case tref: TypeRef => + Some(Ident(tref)) + case baseTpe => + report.error(s"unknown base type ${baseTpe.show} for ancestor ${ancestor.show} of ${cls.symbol.show}") + None + parentTrees.get(ancestor).orElse(baseTree.map(_.withSpan(cls.span))) + ).flatMap { tree => + tree.tpe match { + case Specialization(spec) if spec.hasSpecializedParams && !spec.isFullySpecialized => None // these can only exist in cases where we don't want to inline because: + // 1) they will be pruned out later anyway and if we inline them we will create a loop (as in tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala) + // 2) they are covered by another specialized type parameter in inlining and therefore we will specialize later when the inline function is called + // tests/run/specialized-trait-inline-specialized-instance-with-specialization + // 3) we already dealt with them in desugarSpecializedTraits + case other => Some(tree) + } + } + case _ => + Nil } private def needsTransparentInlining(tree: Tree)(using Context): Boolean = @@ -205,6 +272,139 @@ object Inlines: tree2 end inlineCall + private def updateFlagsFromInlinedParent(child: FlagSet, parent: FlagSet): FlagSet = + var updatedFlags = child + // Parent needs to be initialised so child must also as initialisers have been inlined + if (!parent.is(NoInits)) + updatedFlags &~= NoInits + + // Parent is impure; contaminates child as non-abstract methods have been inlined + if (!parent.is(PureInterface)) + updatedFlags &~= PureInterface + updatedFlags + + + private def checkInnerClasses(tmpl: Template)(using Context) = + tmpl.body.foreach { + // If we want to add these back, some work was done on this in the original Master Thesis + // (https://infoscience.epfl.ch/server/api/core/bitstreams/9413f583-46bc-4106-b994-0be32f20eeba/content) + case innerClass: TypeDef if innerClass.symbol.isClass => report.error("Inline traits may not define inner classes or traits.", innerClass.srcPos) + case _ => + } + + def checkAndTransformInlineTrait(inlineTrait: TypeDef)(using Context): TypeDef = + val tpd.TypeDef(_, tmpl: Template) = inlineTrait: @unchecked + checkInnerClasses(tmpl) + val body1 = tmpl.body.flatMap { + case member: MemberDef => + List(member) + case _ => + // Remove non-memberdefs, as they are normally placed into $init() + Nil + } + val tmpl1 = cpy.Template(tmpl)(body = body1) + cpy.TypeDef(inlineTrait)(rhs = tmpl1) + end checkAndTransformInlineTrait + + + private def checkInlineTraitOverrides(clsSym: ClassSymbol)(using Context) = + /* We need to enforce `override` modifier constraints + here to ensure that the behaviour is the same as ordinary traits. The usual checks only apply + in refChecks which is too late for us. */ + + // TODO: This does cause some code duplication + def checkInlineTraitOverride(member: Symbol, other: Symbol) = + if !member.is(Override) && !other.is(Deferred) && member.owner == clsSym then + report.error( + OverrideError("needs `override` modifier", + other.info, + member, + other, + NoType, + NoType), + member.srcPos + ) + else if member.owner != clsSym && other.owner != clsSym + && !other.owner.derivesFrom(member.owner) + && !(member.isAnyOverride || member.hasAnnotation(defn.UncheckedOverrideAnnot)) + && (!other.is(Deferred) || other.isAllOf(Given | HasDefault)) + && !member.is(Deferred) + && !other.name.is(DefaultGetterName) then + + report.error( + OverrideError( + s"${clsSym} inherits conflicting members:\n " + + err.infoString(other, clsSym.asClass.thisType, showLocation = true) + " and\n " + + err.infoString(member, clsSym.thisType, showLocation = true) + + "\n(Note: this can be resolved by declaring an override in " + clsSym + ".)", + other.info, + member, + other, + NoType, + NoType) + , + clsSym.srcPos + ) + OverridingPairsChecker(clsSym, clsSym.thisType).checkAll(checkInlineTraitOverride) + + def inlineParentInlineTraits(cls: Tree)(using Context): Tree = + cls match { + case cls @ tpd.TypeDef(_, impl: Template) => + checkInlineTraitOverrides(cls.symbol.asClass) + val clsOverriddenSyms = cls.symbol.info.decls.toList.flatMap(_.allOverriddenSymbols).toSet + val ancestors = inlineTraitAncestors(cls) + if cls.symbol.isAnonymousClass && ancestors.exists(tree => Specialization.unapply(tree.tpe).exists(anc => anc.isSpecialized || anc.isFullySpecializedToTopClassesOrNothing)) then + cls // No need to inline into specialized trait anonymous class instances; these will later be replaced by $impl$ classes. + else + val cycleFound = ancestors.exists { parent => + val parentSym = symbolFromParent(parent) + val errorPos = if cls.symbol.ownersIterator.contains(parentSym) then Some(cls.srcPos) // Trying to inline into the tree which defines parentSym (need to catch this separately + // as need to catch it before we inline the second time to avoid tripping an assertion) + else if ctx.inlineTraitState.inlineOrigins(cls.symbol).contains(parentSym) then + val userPos = tpd.enclosingInlineds.last.srcPos // Select the user code that caused this error so we get two errors if there are two problematic inlines, not one + Some(userPos) // Trying to inline into the inlined body of parentSym not in the defn tree + else None // Fine + + errorPos.foreach(pos => + report.error(s"Inlining of inline traits looped. Tried to inline ${parentSym} into its own body.", pos) + ) + errorPos.nonEmpty + } + + if cycleFound then cls + else { + val newDefs = inContext(ctx.withOwner(cls.symbol)) { + ancestors.foldLeft((List.empty[Tree], impl.body)){ + case ((inlineDefs, childDefs), parent) => + val parentTraitInliner = InlineParentTrait(parent) + // Inline body + val overriddenSymbols = clsOverriddenSyms ++ inlineDefs.flatMap(_.symbol.allOverriddenSymbols) + // Need to put the new defs first because we process in linearization order to make overridees correct, + // but we want parent definitions to come first so that if child inline traits refer to values defined in a parent + // inline trait these are defined. + val inlinedDefs1 = parentTraitInliner.expandDefs(overriddenSymbols) ::: inlineDefs + cls.symbol.flags = updateFlagsFromInlinedParent(cls.symbol.flags, parent.symbol.flags) + + val childDefs1 = parentTraitInliner.adaptSuperCalls(childDefs) + (parentTraitInliner.adaptSuperCalls(inlinedDefs1), childDefs1) + } + } + + val newbody = newDefs._1 ::: newDefs._2 + val paramAccessors = newbody.filter(_.symbol.is(ParamAccessor)) + + for pacc <- paramAccessors + otherstat <- newbody if !otherstat.symbol.is(ParamAccessor) && otherstat.denot.matches(pacc.denot.asSingleDenotation) + do report.error(s"Inlining of inline trait created name conflict on ${pacc.denot.name}. Constructor parameters of inline receivers may not collide with members of inline traits.", pacc.srcPos) + + val impl1 = cpy.Template(impl)(body = newbody) + + cpy.TypeDef(cls)(rhs = impl1) + } + case _ => + cls + } + /** Try to inline a pattern with an inline unapply method. Fail with error if the maximal * inline depth is exceeded. * @@ -647,4 +847,290 @@ object Inlines: inlined end expand end InlineCall + + private class InlineParentTrait(parent: tpd.Tree)(using Context) extends Inliner(parent): + import tpd._ + import Inlines.* + + private val parentSym = symbolFromParent(parent) + private val paramAccessorsMapper = ParamAccessorsMapper() + + private val child = ctx.owner + private val childThisType = ctx.owner.thisType + private val childThisTree = This(ctx.owner.asClass).withSpan(parent.span) + + def inlinedSelfType = + inlinerTypeMap(parentSym.asClass.classDenot.givenSelfType) + + def expandDefs(overriddenDecls: Set[Symbol]): List[Tree] = + paramAccessorsMapper.registerParamValuesOf(parent) + val stats = Inlines.defsToInline(parentSym) + .filterNot(stat => overriddenDecls.contains(stat.symbol) && stat.symbol.is(Deferred)) + .filterNot(stat => stat.isDef && stat.symbol.isClass) // Inline traits may not define inner classes; error elsewhere but skip now for best effort error msgs + + val stats1 = stats.map{ // Private symbols must be entered before the RHSs are inlined + case member: MemberDef => Left((member, inlinedSym(member.symbol, overriddenDecls))) + case stat => Right(stat) + } + ctx.owner.info = ctx.owner.asClass.classInfo.integrateOpaqueMembers + stats1.map{ + case Left((tree, inlinedSym)) => expandStat(tree, inlinedSym) + case Right(tree) => inlinedRhs(tree) + } + end expandDefs + + def adaptSuperCalls(defs: List[Tree]) = + val ttmap = TreeTypeMap(treeMap = { + // We go through all ancestor inline traits so eventually we will find the one with matching parentSym + case sel@Select(Super(qual, mix), name) if sel.symbol.owner == parentSym => + // At that point either the method is overridden so needs mangling (and we just copied and mangled it in this inlining phase), + // or not, in which case call directly by original name. In both cases we are calling the method resulting from inlining, on the + // inline receiver class. + Select(This(ctx.owner.asClass), paramAccessorsMapper.getParamAccessorName(sel.symbol.owner, name).getOrElse(name)) + case tree => tree + }) + defs.map(ttmap(_)) + + protected class InlineTraitTypeMap extends InlinerTypeMap { + override def apply(t: Type) = super.apply(t) match { + case t: ThisType if t.cls == parentSym => childThisType + case t => mapOver(t) + } + } + + protected class InlineTraitTreeMap extends InlinerTreeMap { + override def apply(tree: Tree) = super.apply(tree) match { + case tree: This if tree.symbol == parentSym => + Inlined(EmptyTree, Nil, childThisTree).withSpan(parent.span) + case tree: This => + tree.tpe match { + case thisTpe: ThisType if thisTpe.cls.isInlineTrait => + integrate(This(ctx.owner.asClass).withSpan(parent.span), thisTpe.cls) + case _ => + tree + } + case sel@Select(Super(qual, mix), name) => + if sel.symbol.owner.isInlineTrait then // We need to leave this intact so that adaptSuperCalls can redirect it later + sel + else + report.error("Inline traits may not contain superclass references to classes or non-inline traits.", sel.srcPos) + sel + case sel@Select(qual, name) => + inContext(ctx.withSource(tree.source)) { // Need to ensure we preserve the fact that this Select was inlined + // potentially from a different file. Recreating it discards that info. + // See: inline-trait-multiple-stages-generic-defs. + paramAccessorsMapper.getParamAccessorName(qual.symbol, name) match { + case Some(newName) => Select(this(qual), newName).withSpan(parent.span) + case None => Select(this(qual), name) + } + } + case ident: Ident if ident.isType => + // A type parameter of the inline trait written as an identifier in source (e.g. the `S` + // in `new C[S] {}` if nested inside an inline trait) is not handled by the usual inlining machinery + // because S is not a method type parameter. We have our own way of handling this in types + // in the type map but need to also map the tree reference. + // See tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala. + val mapped = inlinerTypeMap(ident.tpe) + if mapped ne ident.tpe then TypeTree(mapped).withSpan(ident.span) + else ident + case tree => + tree + } + } + + override protected val inlinerTypeMap: InlinerTypeMap = InlineTraitTypeMap() + override protected val inlinerTreeMap: InlinerTreeMap = InlineTraitTreeMap() + + override protected def inlineCopier: tpd.TreeCopier = new TypedTreeCopier() { + // FIXME it feels weird... Is this correct? + override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply = + untpd.cpy.Apply(tree)(fun, args).withTypeUnchecked(tree.tpe) + } + + override protected def computeThisBindings(): Unit = () + override protected def canElideThis(tpe: ThisType): Boolean = true + + override protected def inlineCtx(inlineTyper: InlineTyper)(using Context): Context = + ctx.fresh.setTyper(inlineTyper).setNewScope + + extension (sym: Symbol) + private def isTermParamAccessor: Boolean = !sym.isType && sym.is(ParamAccessor) + + private def expandStat(stat: tpd.Tree, inlinedSym: Symbol)(using Context): tpd.Tree = stat match + case stat: ValDef => + inlinedValDef(stat, inlinedSym) + case stat: DefDef => + inlinedDefDef(stat, inlinedSym) + case stat: TypeDef => + inlinedTypeDef(stat, inlinedSym) + + private def inlinedSym(sym: Symbol, overriddenDecls: Set[Symbol], withoutFlags: FlagSet = EmptyFlags)(using Context): Symbol = + assert(!sym.isClass) + inlinedMemberSym(sym, overriddenDecls, withoutFlags) + + private def inlinedMemberSym(sym: Symbol, overriddenDecls: Set[Symbol], withoutFlags: FlagSet = EmptyFlags)(using Context): Symbol = + var name = sym.name + var flags = sym.flags | Synthetic + if sym.isTermParamAccessor then flags &~= ParamAccessor + if sym.is(Local) || (overriddenDecls.contains(sym)) then + name = paramAccessorsMapper.registerNewName(sym) + flags |= (Private | Local) + flags &~= Override // private override is illegal; if the inlined method was already override then we might make it private override by accident. + else + flags |= Override + sym.copy( + owner = ctx.owner, + name = name, + flags = flags &~ withoutFlags, + info = inlinerTypeMap(sym.info), + coord = spanCoord(parent.span)).entered + + private def inlinedValDef(vdef: ValDef, inlinedSym: Symbol)(using Context): ValDef = + val rhs = + paramAccessorsMapper + .getParamAccessorRhs(vdef.symbol.owner, vdef.symbol.name) + .getOrElse(inlinedRhs(vdef, inlinedSym)) + + val rhs1 = rhs.changeNonLocalOwners(inlinedSym) // if rhs.symbol.exists then rhs.changeOwner(rhs.symbol.owner, inlinedSym) else rhs + + tpd.ValDef(inlinedSym.asTerm, rhs1).withSpan(parent.span) + + private def inlinedDefDef(ddef: DefDef, inlinedSym: Symbol)(using Context): DefDef = + val rhsFun: List[List[Tree]] => Tree = + if ddef.symbol.isSetter then + _ => unitLiteral + else + paramss => + val oldParamSyms = ddef.paramss.flatten.map(_.symbol) + val newParamSyms = paramss.flatten.map(_.symbol) + val ddef1 = cpy.DefDef(ddef)(rhs = ddef.rhs.subst(oldParamSyms, newParamSyms)) + inlinedRhs(ddef1, inlinedSym) + tpd.DefDef(inlinedSym.asTerm, rhsFun).withSpan(parent.span) + + private def inlinedTypeDef(tdef: TypeDef, inlinedSym: Symbol)(using Context): TypeDef = + if inlinedSym.isOpaqueAlias then + val inlinedRhsType = inlinerTypeMap(tdef.rhs.tpe) + inlinedSym.info = inlinedSym.opaqueToBounds(TypeAlias(inlinedRhsType), tdef.rhs, List()) + inlinedSym.typeRef.recomputeDenot() + ctx.typeAssigner.assignType(untpd.TypeDef(inlinedSym.name.asTypeName, TypeTree(inlinedRhsType)), inlinedSym).withSpan(parent.span) + else + tpd.TypeDef(inlinedSym.asType).withSpan(parent.span) + + + private def inlinedRhs(vddef: ValOrDefDef, inlinedSym: Symbol)(using Context): Tree = + val rhs = vddef.rhs.changeOwner(vddef.symbol, inlinedSym) + inlinedRhs(rhs)(using ctx.withOwner(inlinedSym)) + + private def inlinedRhs(rhs: Tree)(using Context): Tree = + if rhs.isEmpty then + rhs + else + val symbolMap = mutable.Map[Symbol, Symbol]() + // TODO make version of inlined that does not return bindings? + val rhs1 = Inlined(tpd.ref(parentSym).withSpan(parent.span), Nil, inlined(rhs)._2.withSpan(parent.span).cloneIn(parentSym.source)).withSpan(parent.span) // TODO: This inlines also calls to inline defs that were made in the inline trait body, is that desirable? + + // In case of nested inline trait inlines, because BodyAnnotation is out of date, + // body inlined misses nested expansion, but we have the symbols for the items that should be there + // Remove them so that they can be inlined properly later. + val ttmap = TreeTypeMap(treeMap = { + case tree@TypeDef(name, tmpl: Template) if Inlines.needsInlining(tree) => + val newSym = tree.symbol.copy(coord = spanCoord(tree.span)) // Coord should correspond to original location because we will inline from there. + newSym.info = ClassInfo(tree.symbol.owner.thisType, newSym.asClass, tree.symbol.asClass.parentTypes, Scopes.newScope) + + val newConstructorSymbol = tree.symbol.primaryConstructor.copy(owner = newSym) + val rt = newSym.typeRef.appliedTo(newSym.typeParams.map(_.typeRef)) + def resultType(tpe: Type): Type = tpe match { + case mt @ MethodType(paramNames) => mt.derivedLambdaType(paramNames, mt.paramInfos, rt) + case pt : PolyType => pt.derivedLambdaType(pt.paramNames, pt.paramInfos, resultType(pt.resType)) + } + newConstructorSymbol.info = resultType(newConstructorSymbol.info) + newConstructorSymbol.info = PolyType.fromParams(newConstructorSymbol.owner.typeParams, newConstructorSymbol.info) + + symbolMap(tree.symbol.primaryConstructor) = newConstructorSymbol + + val childSyms = tree.symbol.info.decls + .filter(sym => tmpl.body.exists(vddef => vddef.symbol == sym)) + .tapEach(sym => symbolMap(sym) = sym.copy(owner = newSym, coord=sym.coord)) + .map(symbolMap) + + childSyms.foreach(p => p.entered) + newConstructorSymbol.entered + + val tmpl1 = tmpl.changeOwner(tree.symbol, newSym) + + val rhsFun: List[List[Tree]] => Tree = + paramss => + val oldParamSyms = tmpl1.constr.paramss.flatten.map(_.symbol) + val newParamSyms = paramss.flatten.map(_.symbol) + tmpl1.constr.rhs.subst(oldParamSyms, newParamSyms) + + val ctor = tpd.DefDef(newConstructorSymbol.asTerm, rhsFun) + symbolMap(tree.symbol) = newSym + + ctx.inlineTraitState.registerInlineOrigin(newSym, child, parentSym) + + tpd.ClassDefWithParents(newSym.asClass, ctor, tmpl1.parents, tmpl1.body) + case tree => tree + }) + + val rhs2 = ttmap(rhs1) + TreeTypeMap(substFrom = symbolMap.keys.toList, + substTo = symbolMap.values.toList, + oldOwners = symbolMap.keys.toList, + newOwners = symbolMap.values.toList)(rhs2) + + private class ParamAccessorsMapper: + private val paramAccessorsTrees: mutable.Map[Symbol, Map[Name, Tree]] = mutable.Map.empty + private val paramAccessorsNewNames: mutable.Map[(Symbol, Name), Name] = mutable.Map.empty + + def registerParamValuesOf(parent: Tree): Unit = + def allArgs(tree: Tree, acc: Vector[List[Tree]]): List[List[Tree]] = tree match + case Apply(fun, args) => allArgs(fun, acc :+ args) + case TypeApply(fun, _) => allArgs(fun, acc) + case _ => acc.toList + def allParams(info: Type, acc: List[List[Name]]): List[List[Name]] = info match + case mt: MethodType => allParams(mt.resultType, mt.paramNames :: acc) + case pt: PolyType => allParams(pt.resultType, acc) + case _ => acc + val info = + if parent.symbol.isClass then parent.symbol.primaryConstructor.info + else parent.symbol.info + val paramAccessors = allParams(info, Nil).flatten.zip(allArgs(parent, Vector.empty).flatten).toMap + paramAccessorsTrees.put(symbolFromParent(parent), paramAccessors) + + def registerNewName(paramAccessorSym: Symbol): paramAccessorSym.ThisName = + val oldName = paramAccessorSym.name + val newName = oldName.expandedName(parentSym) + paramAccessorsNewNames.put((paramAccessorSym.owner, oldName), newName) + newName + + def getParamAccessorRhs(parent: Symbol, paramAccessorName: Name): Option[Tree] = + paramAccessorsTrees.get(parent).flatMap(_.get(paramAccessorName)) + + def getParamAccessorName(parent: Symbol, paramAccessorName: Name): Option[Name] = + paramAccessorsNewNames.get(parent, paramAccessorName) + end ParamAccessorsMapper + end InlineParentTrait + + + object InlineTraitState: + enum InlineContext: + case InlineTraits, SpecializedTraits, None + + class InlineTraitState( + // For a class symbol created during inlining of an inline trait, + // the chain of inlined traits which produced it. We don't actually care about the order. + // Used as a "seen list" for cycle checking. Persists across invocations of InlineParentTrait + val inlineOrigins: mutable.Map[Symbol, Set[Symbol]] = mutable.HashMap[Symbol, Set[Symbol]]().withDefaultValue(Set.empty), + val inlineTraitsPhase: InlineTraitState.InlineContext = InlineTraitState.InlineContext.None + ): + + def registerInlineOrigin(newSym: Symbol, owner: Symbol, parentSym: Symbol): Unit = + inlineOrigins(newSym) = inlineOrigins(owner) + parentSym + + def copyInPhase(phase: InlineContext) = + InlineTraitState(inlineOrigins, phase) + + end InlineTraitState + end Inlines diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index d1caf2bf9605..7c8f46e4dac5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -247,6 +247,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case IndentationWarningID // errorNumber: 229 case IllegalIdentifierID // errorNumber: 230 case ConcreteClassHasUnimplementedMethodsID // errorNumer: 231 + case IllegalUseOfSpecializedID // errorNumber: 232 + case VarianceInSpecializedTraitsLimitationID // errorNumber: 233 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 ac2419d4cfe5..9955ce168c3a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3935,3 +3935,69 @@ extends Message(ConcreteClassHasUnimplementedMethodsID), NoDisambiguation: def explain(using Context) = "" override def actions(using Context) = methodActions + +final class IllegalUseOfSpecialized(using Context) + extends SyntaxMsg(IllegalUseOfSpecializedID): + override protected def msg(using Context): String = + i"Specialized may only be used as a context bound" + override protected def explain(using Context): String = + i"""Specialized allows for a performance improvement by specializing generic type parameters + to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + generic type in inline traits or inline methods: + + inline trait Vec[T: Specialized](val x: T) + + inline def foo[T: Specialized](v: Vec[T]) = v.x + + In this instance it was used in a way which is unsupported, such as + trying to create a type synonym or a value with explicit type Specialized[X]. + """ + +/** Shows up as a TypeError (in the notes field) if variance is attempted + * in a way which is incompatible with specialized traits. */ +final class IllegalVarianceInSpecializedTraitsNote(using Context) extends Note: + def render(using Context): String = + i""" + This use of variance is incompatible with specialized traits. + + Specialized traits achieve a performance gain through a special erasure. + - Primitives are specialized: Foo[Int] erases to Foo$$sp$$Int + - Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$$sp$$Animal + - Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo. + This means that variance patterns that cross these erasure categories will fail at + runtime due to a ClassCastException, so they are not permitted. + + Please see the docs for more information on how specialized traits are erased. + Suggested fixes: + - Make the type of the target site more general e.g. Foo[Object] instead of Foo[Animal]. + - Reconsider if you really need to use Nothing / Object / Any / AnyRef / AnyVal in your code. + - Remove Specialized from the definition of the corresponding parameter. + """ + + override def covers(other: Note)(using Context): Boolean = + other.isInstanceOf[IllegalVarianceInSpecializedTraitsNote] + +final class VarianceInSpecializedTraitsLimitation(using Context) + extends Message(VarianceInSpecializedTraitsLimitationID): + override def kind = MessageKind.PotentialIssue + override protected def msg(using Context): String = + i"Type parameter is both Specialized and variant. This imposes additional typing restrictions." + override protected def explain(using Context): String = + i"""Specialized traits achieve a performance gain through a special erasure. + - Primitives are specialized: Foo[Int] erases to Foo$$sp$$Int + - Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$$sp$$Animal + - Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo. + This means that certain variance patterns that cross these erasure categories will fail at + runtime due to a ClassCastException, so they are not permitted. + + For example, treating Foo[Any] as Foo[Animal] via contravariance is not allowed with Specialized. + + Please see the docs for more information on how specialized traits are erased. + + If you accept this limitation you can silence this warning with @nowarn. For example: + + @nowarn("id=E${VarianceInSpecializedTraitsLimitationID.errorNumber}") + inline trait Foo[-T: Specialized]: + + Otherwise, remove Specialized, or remove the variance. + """ diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 999295d8cbe6..69328acd7c5b 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -819,10 +819,14 @@ private class ExtractAPICollector(nonLocalClassSymbols: mutable.HashSet[Symbol]) p match case ref: RefTree @unchecked => val sym = ref.symbol - if sym.is(Inline, butNot = Param) && !seenInlineCache.contains(sym) then + if sym.is(Inline, butNot = Param | Trait) && !seenInlineCache.contains(sym) then // An inline method that calls another inline method will eventually inline the call // at a non-inline callsite, in this case if the implementation of the nested call // changes, then the callsite will have a different API, we should hash the definition + // In contrast, if an inline trait A extends an inline trait B, B's body is inlined into + // A, so a change to B will cause a change to A directly, so we can skip this, and if + // an inline method accesses a parameter of an inline trait it doesn't care about the definition + // of the trait. h = MurmurHash3.mix(h, apiDefinition(sym, inlineOrigin).hashCode) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/Bridges.scala b/compiler/src/dotty/tools/dotc/transform/Bridges.scala index 9a76a5a6be9e..e2bad9415de4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Bridges.scala +++ b/compiler/src/dotty/tools/dotc/transform/Bridges.scala @@ -12,6 +12,7 @@ import ContextFunctionResults.{contextResultCount, contextFunctionResultTypeAfte import StdNames.nme import Constants.Constant import TypeErasure.transformInfo +import TypeErasure.disallowSpecializedCtx import Erasure.Boxing.adaptClosure /** A helper class for generating bridge methods in class `root`. */ @@ -19,7 +20,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) { import ast.tpd.* assert(ctx.phase == erasurePhase.next) - private val preErasureCtx = ctx.withPhase(erasurePhase) + private val preErasureCtx = disallowSpecializedCtx(using ctx.withPhase(erasurePhase.prev)) private lazy val elimErasedCtx = ctx.withPhase(elimErasedValueTypePhase.next) private class BridgesCursor(using Context) extends OverridingPairs.Cursor(root) { diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index 2e26c7f71308..61220b503ceb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -242,7 +242,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = constrStats += intoConstr(stat, sym) } else dropped += sym - case stat @ DefDef(name, _, tpt, _) if stat.symbol.isGetter && !stat.symbol.is(Lazy) => + case stat @ DefDef(name, _, tpt, _) if stat.symbol.isGetter && !stat.symbol.is(Lazy) && !stat.symbol.owner.isInlineTrait => val sym = stat.symbol assert(isRetained(sym), sym) if sym.isConstExprFinalVal then diff --git a/compiler/src/dotty/tools/dotc/transform/DesugarSpecializedTraits.scala b/compiler/src/dotty/tools/dotc/transform/DesugarSpecializedTraits.scala new file mode 100644 index 000000000000..3b9c6317815c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DesugarSpecializedTraits.scala @@ -0,0 +1,728 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.i +import dotty.tools.dotc.core.Decorators.className +import dotty.tools.dotc.core.Symbols.{Symbol, ClassSymbol, newNormalizedClassSymbol} +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Symbols.newClassSymbol +import scala.Function.const +import dotty.tools.dotc.core.Names.TypeName +import dotty.tools.dotc.core.Symbols.TypeSymbol +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.ast.untpd +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.core.Flags.EmptyFlags +import dotty.tools.dotc.ast.TreeTypeMap +import dotty.tools.dotc.core.Scopes.EmptyScope +import dotty.tools.dotc.core.StdNames.str.SPECIALIZED_TRAIT_SUFFIX +import dotty.tools.dotc.core.Names.Name +import tpd._ +import scala.collection.mutable +import scala.annotation.unspecialized +import dotty.tools.dotc.typer.Synthesizer +import dotty.tools.dotc.core.NameKinds +import dotty.tools.dotc.core.Flags.GivenOrImplicit +import dotty.tools.dotc.core.NameKinds.ContextBoundParamName +import dotty.tools.dotc.core.NameKinds.FlatName +import dotty.tools.dotc.inlines.Inlines +import dotty.tools.dotc.util.Spans.Span +import dotty.tools.dotc.report +import dotty.tools.dotc.core.Flags.InlineTrait +import dotty.tools.dotc.core.Annotations.Annotation +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.util.SrcPos +import dotty.tools.dotc.core.Decorators.nestedMap +import dotty.tools.dotc.core.NameOps.expandedName +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.Flags.InlineMethod +import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer +import dotty.tools.dotc.core.Names.TermName +import dotty.tools.dotc.util.Spans.spanCoord +import dotty.tools.dotc.util.Spans.NoSpan +import dotty.tools.dotc.transform.DesugarSpecializedTraits.specType +import dotty.tools.dotc.transform.DesugarSpecializedTraits.isTopClass +import dotty.tools.dotc.reporting.VarianceInSpecializedTraitsLimitation +import dotty.tools.dotc.transform.DesugarSpecializedTraits.isTopClassOrNothing +import dotty.tools.dotc.inlines.Inlines.InlineTraitState + +class DesugarSpecializedTraits extends MiniPhase, IdentityDenotTransformer: + + override def phaseName: String = DesugarSpecializedTraits.name + override def description: String = DesugarSpecializedTraits.description + override def changesMembers: Boolean = false + override def changesParents: Boolean = true + + override def allowsImplicitSearch: Boolean = true + + private def newInterfaceTrait(specialization: Specialization, specializations: SpecializedTraitCache)(using Context): (ClassSymbol, SpecializedTraitCache) = { + val tm = new TypeMap: // TODO: Can we get this into the specialization ideally. + def apply(t: Type) = specialization.specializedTypeParamsToTypeArgumentsMap.view.mapValues(_.tpe).applyOrElse(t, mapOver) // TODO: If we can do just types we can get rid of this + + val inheritedParents = specialization.traitSymbol.denot.info.parents.filterNot(_.typeSymbol == defn.ObjectClass).map(tm(_)) + // Parents may be specializable and so we need to specialize them as well + // See ArrayIterator extends Iterator in specialized-trait-collections-example.scala + val specializations1 = inheritedParents.foldLeft(specializations)((specializations, parent) => + (parent, specialization.span) match { + case Specialization(spec) if spec.isSpecialized => specializations.addInterface(spec) + case _ => specializations + } + ) + + // Order is depended on in Erasure::typedClassDef and TypeErasure:eraseParent + val parents = defn.ObjectType + :: AppliedTypeTree(Ident(specialization.traitSymbol.typeRef), specialization.specialization).tpe // original trait, specialized to Foo[Int] + :: inheritedParents // parents of the original trait in the form Foo[Int] (later specialized to Foo$sp$Int) + + // Create new trait + val traitSymbol = newNormalizedClassSymbol( + specialization.traitSymbol.owner.enclosingPackageClass, // For specialized traits defined inside objects/classes etc, pre-Flatten the $sp$ and $impl$ def trees (i.e. + // make them live in the enclosing package with the flattened name). We do this because it's easier than + // finding the defining tree of the object, which would require scanning the whole file, and it + // might be in another compilation unit / already compiled. + DesugarSpecializedTraits.newSpecializedTraitName(specialization), + Flags.Synthetic | Flags.Trait | Flags.Inline, + parents, + tm(specialization.traitSymbol.asClass.classInfo.selfType), + specialization.traitSymbol.privateWithin, + spanCoord(specialization.span), // TODO: Show errors where they actually show up in the inline trait and not at the type of the user which is very confusing. Need to use inline stack + specialization.traitSymbol.compilationUnitInfo + ) + + buildTypeParameters(traitSymbol, specialization) + (traitSymbol.entered, specializations1) + } + + private def buildInterfaceTraitTree(specialization: Specialization, interfaceSymbol: ClassSymbol)(using Context) = { + val init = newConstructor(interfaceSymbol, EmptyFlags, Nil, Nil, coord=spanCoord(specialization.span)) + fixConstructor(init, interfaceSymbol) + ClassDef(interfaceSymbol, DefDef(init.entered), Nil).withSpan(specialization.span) + } + + /* Fix constructor so that it: + 1) Has correct generic type parameters + 2) Returns the correct type corresponding to those type parameters applied + 3) Has correct parameter names corresponding to targetParamNames */ + private def fixConstructor(init: Symbol, traitOrClassSymbol: ClassSymbol, targetParamNames: List[List[TermName]] = List())(using Context) = + val rt = traitOrClassSymbol.typeRef.appliedTo(traitOrClassSymbol.typeParams.map(_.typeRef)) + def resultType(tpe: Type, targetParamNames: List[List[TermName]]): Option[Type] = + tpe match { + case mt @ MethodType(paramNames) => targetParamNames match { + case head :: tail => Some(mt.derivedLambdaType(head, mt.paramInfos, resultType(mt.resultType, tail).getOrElse(rt))) + case Nil => Some(mt.derivedLambdaType(paramNames, mt.paramInfos, resultType(mt.resultType, targetParamNames).getOrElse(rt))) + } + case pt : PolyType => Some(pt.derivedLambdaType(pt.paramNames, pt.paramInfos, resultType(pt.resType, targetParamNames).get)) + case _ => None + } + init.info = resultType(init.info, targetParamNames).get + init.info = PolyType.fromParams(init.owner.typeParams, init.info) + + private def buildTypeParameters(traitOrClassSymbol: ClassSymbol, specialization: Specialization)(using Context) = + val tps = newTypeParams(traitOrClassSymbol, + specialization.unspecializedTypeParams.map(_.typeSymbol.name.asTypeName), + EmptyFlags, + targets => targets.map(t => specialization.traitSymbol.typeParams.find(_.name == t.name).get.info.bounds) + ) + tps.foreach(traitOrClassSymbol.enter(_, EmptyScope)) + + // Replace old type parameters that were copied from original trait with new ones + // inside the parents of the new trait + val tpMap: Map[Type, Type] = specialization.unspecializedTypeParams.zip(tps.map(_.typeRef)).toMap + val freshTypeVarMap = new TypeMap: + def apply(t: Type) = tpMap.applyOrElse(t, mapOver) + + def mapSelfType(st: Type | Symbol): Type | Symbol = + if st.isInstanceOf[Symbol] then + st.asInstanceOf[Symbol].copy(info = freshTypeVarMap(st.asInstanceOf[Symbol].info)) + else + freshTypeVarMap(st.asInstanceOf[Type]) + + traitOrClassSymbol.info = ClassInfo(traitOrClassSymbol.owner.thisType, traitOrClassSymbol, traitOrClassSymbol.info.parents.map(freshTypeVarMap(_)), traitOrClassSymbol.info.decls, mapSelfType(traitOrClassSymbol.classInfo.selfInfo)) + + // Order is depended on in Erasure::typedClassDef and TypeErasure:eraseParent + private def generateImplementationClassParents(specialization: Specialization, interfaceSymbol: Option[ClassSymbol])(using Context) = + val objectParent = defn.ObjectType + val traitSpParent = interfaceSymbol.map(_.typeRef.appliedTo(specialization.unspecializedTypeParams)) // Set using old unspecializedTypeParams and replace after. + val originalTraitSpecializedParent = AppliedTypeTree(Ident(specialization.traitSymbol.typeRef), specialization.mapSpecializedUnspecializedArgs(tr => TypeTree(specType(tr.tpe)), specialization.unspecializedTypeParams.map(TypeTree(_)))).tpe + (objectParent, traitSpParent, originalTraitSpecializedParent) + + private def newImplementationClass(specialization: Specialization, interfaceSymbol: Option[ClassSymbol])(using Context) = + val (objectParent, traitSpParent, originalTraitSpecializedParent) = generateImplementationClassParents(specialization, interfaceSymbol) + val parents = if traitSpParent.nonEmpty then List(objectParent, traitSpParent.get, originalTraitSpecializedParent) else List(objectParent, originalTraitSpecializedParent) + + val newImplementationClassSymbol = newNormalizedClassSymbol( + specialization.traitSymbol.owner.enclosingPackageClass, + DesugarSpecializedTraits.newImplementationClassName(specialization), + Flags.Synthetic, + parents, + NoType, + specialization.traitSymbol.privateWithin, + spanCoord(specialization.span), + specialization.traitSymbol.compilationUnitInfo + ) + + specialization.traitSymbol.addAnnotation(Annotation.Child(newImplementationClassSymbol, newImplementationClassSymbol.span.startPos)) + + buildTypeParameters(newImplementationClassSymbol, specialization) + + newImplementationClassSymbol.entered + + // TODO: Do we want to share some code with the newSpecializedInterfaceTrait and buildInterfaceTraitTree? + // TODO: Tidy this up a bit with functions + // intefaceSymbol: None if no interface; this only happens with the fully non-specialized $impl$ (raw) case + private def buildImplementationClassTree(specialization: Specialization, interfaceSymbol: Option[ClassSymbol], classSymbol: ClassSymbol)(using Context) = { + val (objectParent, traitSpParent_, originalTraitSpecializedParent_) = generateImplementationClassParents(specialization, interfaceSymbol) + + // Apply Type Param Fix: TODO : This really ought to be done more cleanly somewhere else. + val tpMap: Map[Type, Type] = specialization.unspecializedTypeParams.zip(classSymbol.typeParams.map(_.typeRef)).toMap + val freshTypeVarMap = new TypeMap: + def apply(t: Type) = tpMap.applyOrElse(t, mapOver) + val traitSpParent = traitSpParent_.map(tp => freshTypeVarMap(tp)) + val originalTraitSpecializedParent = freshTypeVarMap(originalTraitSpecializedParent_) + + val init = newConstructor(classSymbol, EmptyFlags, Nil, Nil, coord=spanCoord(specialization.span)) + val tm = new TypeMap: // TODO: Can we get this into the specialization ideally. + def apply(t: Type) = specialization.specializedConstructorParamToArgumentTypeMap.applyOrElse(t, mapOver) + + /* Create constructor and setup constructor type */ + val nonTypeParams = specialization.traitSymbol.primaryConstructor.rawParamss.tail + val oldTypeParams = specialization.unspecializedConstructorParams + val initTypeParams = classSymbol.typeParams.map(s => s.copy(owner = init, flags = (s.flags &~ (Flags.Private | Flags.Deferred)))) + val valueParams = nonTypeParams.map(_.map(param => param.copy(owner = init, info = tm(param.info).substSym(oldTypeParams, initTypeParams), name=param.name.expandedName(classSymbol)))) // We need to map the parameter names to avoid a name clash with val params from parents (see tests/pos/specialized-trait-val-parameter.scala) + + initTypeParams.foreach(_.entered) + init.setParamss(initTypeParams :: valueParams) + init.info = specialization.traitSymbol.primaryConstructor.info.appliedTo( // Type Arg if specialized; otherwise we want our type param. + specialization.constructorTypeParams.map(par => specialization.specializedConstructorParamToArgumentTypeMap.applyOrElse(par, _.subst(oldTypeParams, classSymbol.typeParams.map(_.typeRef)))) + ) + fixConstructor(init, classSymbol, valueParams.map(_.map(_.name.asTermName))) + + /* Build param accessors */ + val paramAccessorss = valueParams.map(params => params.map(s => s.copy(owner = classSymbol, flags=(s.flags|Flags.LocalParamAccessor) &~ Flags.Param, info = s.info.subst(initTypeParams, classSymbol.typeParams.map(_.typeRef))))) + paramAccessorss.foreach(_.foreach(classSymbol.enter(_))) + + /* Build class def tree */ + val newParamss = paramAccessorss.nestedMap(ref(_)) + val newParams1 = if (newParamss.length == 1) then newParamss ++ List(List()) else newParamss + + // Re-expand varargs parameters from Seq[T] to *T for passing into parent constructor + val newParams2 = newParams1.nestedMap( param => + if param.symbol.info.hasAnnotation(defn.RepeatedAnnot) then ctx.typer.seqToRepeated(param) else param + ) + + val opTree = New(objectParent, objectParent.classSymbol.primaryConstructor.asTerm, Nil) + val tspTree = traitSpParent.map(tsp => New(tsp, tsp.classSymbol.primaryConstructor.asTerm, Nil)) + val opSpTree = New(originalTraitSpecializedParent.typeConstructor) + .select(TermRef(originalTraitSpecializedParent.typeConstructor, specialization.traitSymbol.primaryConstructor.asTerm)) + .appliedToTypes(originalTraitSpecializedParent.argTypes) + .appliedToArgss(newParams2) + + // TODO: Clean and robust + ClassDefWithParents( + classSymbol, + DefDef(init.asTerm.entered), + if tspTree.nonEmpty then List(opTree, tspTree.get, opSpTree) + else List(opTree, opSpTree), + // Put into body of class + paramAccessorss.flatMap(syms => syms.map(sym => tpd.ValDef(sym.asTerm))) + ).withSpan(specialization.span) + } + + // Returns (new stmts including original, new symbols including original) + private def transformStatements(stats1: List[Tree], specializations: SpecializedTraitCache)(using Context): (List[Tree], SpecializedTraitCache) = { + + val inlineSpecializedMethods = new TreeMapWithPreciseStatContexts { + override def transform(tree: Tree)(using Context): Tree = tree match { + case MethodSpecialization(methSpec) if methSpec.isSpecialized || methSpec.isFullySpecializedToTopClassesOrNothing => + // TODO: Not sure why we needed this - it doesn't seem to make any difference + def flattenTree(inlinedTree: Tree): Tree = inlinedTree match { + case it@Inlined(call, bindings, expansion) => + val callTrace = Inlines.inlineCallTrace(tree.symbol, inlinedTree.sourcePos)(using ctx.withSource(inlinedTree.source)) + cpy.Inlined(it)(callTrace, bindings, expansion)(using inlineContext(it)) + case Block(stats, expr) => Block(stats, flattenTree(expr)) + case t => t // if inlining failed due to max inlines reached + } + + val inlinedTree = Inlines.inlineCall(tree) + super.transform(flattenTree(inlinedTree)) + case tree => super.transform(tree) + } + } + + val stats = inlineSpecializedMethods.transform(stats1) + + val specializations1 = collectReferencedSpecializations(stats, specializations) + val generatedTraitStats = specializations1.getNewInterfaceSymbols.toList.map(buildInterfaceTraitTree) + val generatedClassStats = specializations1.getNewImplementationSymbols.toList.map(buildImplementationClassTree) + + val specializations2 = specializations1.installNewInterfaceSymbols.installNewImplementationSymbols + + val generatedTraitStats1 = generatedTraitStats.map { + case tree: TypeDef => + assert(tree.symbol.isInlineTrait) + val inlined = Inlines.inlineParentInlineTraits(Inlines.checkAndTransformInlineTrait(tree)).asInstanceOf[TypeDef] + + // Inlined body may contain references to inline traits that need to be inlined as well + // See tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala + // specializeInlineTraits is responsible for inlining into D because it's not $sp$ or $impl$ + // Because this code is synthetic we won't run the phase on this code as part of the usual + // megaphase transform so we need to do it manually. + transformFollowing(inlined)(using ctx.fresh.setInlineTraitState(ctx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.InlineTraits))) + } + + val generatedClassStats1 = generatedClassStats.map { + case tree: TypeDef => + val inlined = Inlines.inlineParentInlineTraits(tree).asInstanceOf[TypeDef] + transformFollowing(inlined)(using ctx.fresh.setInlineTraitState(ctx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.InlineTraits))) + } + + val (generatedTraitStatsFinal, generatedClassStatsFinal, specializationsFinal) = + if (generatedTraitStats1.isEmpty && generatedClassStats1.isEmpty) + (generatedTraitStats1, generatedClassStats1, specializations2) + else + val (generatedTraitStats2, specializations3) = transformStatements(generatedTraitStats1, specializations2) + val (generatedClassStats2, specializations4) = transformStatements(generatedClassStats1, specializations3) + + (generatedTraitStats2, generatedClassStats2, specializations4) + + (generatedTraitStatsFinal ++ generatedClassStatsFinal ++ stats, specializationsFinal) // TODO: Since we only change stats by the inlining we could potentially arguably "undo" the inlining and then redo it at the "correct" point later - + // so we don't actually modify the tree in that way here; not sure if that's worth doing (it would be throwing away work) + } + + private def checkSpecializedTraitRules(tree: Tree)(using Context) = + def checkType(t: Type, pos: SrcPos) = t.widen.dealias match { + case SpecializedEvidence(_) => + report.error(s"Only inline traits and inline functions may take Specialized type parameters", pos) + case _ => + } + + tree.foreachSubTree { // TODO: This is not particularly efficient + case d@DefDef(name, paramss, tpt, preRhs) if d.symbol.isConstructor && !d.symbol.owner.is(Flags.Inline) => d.paramss.flatten.foreach(p => checkType(p.tpe, d.srcPos)) + case d@DefDef(name, paramss, tpt, preRhs) if !d.symbol.isConstructor && !d.symbol.is(Flags.Inline) => d.paramss.flatten.foreach(p => checkType(p.tpe, d.srcPos)) + case AnonymousClassInstance(anon) => + def deandify(tp: Type): Iterator[Type] = tp match + case AndType(l, r) => deandify(l) ++ deandify(r) + case _ => Iterator.single(tp) + anon.typeTree.tpe match { + case a: AndType => /* Multiple mixed in traits will be typed as an AndType */ + deandify(a).foreach(trt => + Specialization.unapply(trt, anon.typeTree.span).foreach {spec => + if spec.hasSpecializedParams then + report.error("Anonymous classes acting as instances of Specialized traits may not mix in other traits; you can make a named object instead if you like.", anon.srcPos) + } + ) + case tpe => + Specialization.unapply(tpe, anon.typeTree.span).map(spec => + { + if spec.hasSpecializedParams then + if anon.body.filterNot(x => x.symbol.name.is(ContextBoundParamName)).nonEmpty then // Only allowed to contain evidence parameters + report.error("Anonymous classes acting as instances of Specialized traits may not have additional members; you can make a named object instead if you like.", anon.srcPos) + + anon.parentCalls match { + case (obj :: parentsOfSpecTrait) :+ (app@Apply(_, _)) if (obj.symbol.owner == ctx.definitions.ObjectClass) && (parentsOfSpecTrait.forall(x => spec.traitSymbol.asClass.baseClasses.exists(p => p == x.symbol.owner))) => + case _ => report.error("Anonymous classes acting as instances of Specialized traits may not mix in other traits; you can make a named object instead if you like.", anon.srcPos) + } + else + tree + }).getOrElse(tree) + } + + case _ => + } + + override def prepareForUnit(tree: Tree)(using Context): Context = + ctx.fresh.setInlineTraitState(ctx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.SpecializedTraits)) + + override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = + tree match { // TODO: Is Package level processing really what we want? Given we are going to output the classes somewhere else do we not really want either to deepFold the whole tree directly or do a more direct transform? + case pkg@PackageDef(pid, stats) => // TODO: If we do everything ourselves and match only on the package then we can get rid of the MacroTransform aspect and just have a Phase with the transformPackageDef method or even transformStats ideally + + checkSpecializedTraitRules(tree) + + if ctx.specializedTraitState.specializedTraitCache.isEmpty then + ctx.specializedTraitState.specializedTraitCache = Some(SpecializedTraitCache(genInterfaceSymbol = newInterfaceTrait, genImplementationSymbol = newImplementationClass)) + + val (stats1, specializedTraitCache2) = transformStatements(stats, ctx.specializedTraitState.specializedTraitCache.get) + ctx.specializedTraitState.specializedTraitCache = Some(specializedTraitCache2) // TODO: Avoid mutation here - we will make the cache mutable instead I think. Makes more sense + + val grouped = stats1.groupBy(tree => tree.symbol.enclosingPackageClass) + + cpy.PackageDef(pkg)(Ident(defn.EmptyPackageVal.namedType), // We need to copy the existing package so we don't lose any attachments (e.g. attachments used to calculate Wunused) + grouped.getOrElse(defn.RootClass, List()) ::: + grouped.getOrElse(defn.EmptyPackageClass, List()) ::: + grouped.toList.filter((pk, stmts) => pk != defn.RootClass && pk != defn.EmptyPackageClass).map((pkg, stmts) => tpd.PackageDef(Ident(pkg.sourceModule.namedType), stmts)) + ).withType(defn.EmptyPackageVal.namedType) + } + + private def collectReferencedSpecializations(stats: List[Tree], specializations: SpecializedTraitCache)(using Context): SpecializedTraitCache = + stats.foldLeft(specializations)((specializations, tree) => { + tree.deepFold(specializations)((specializations, tree) => tree match + case t@TypeDef(name, tmpl: Template) if t.symbol.isSpecializedTrait => + if !t.symbol.isStatic then + // The approach we use for flattening makes this quite tricky: see e.g. tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala. + // In theory can scan the tree to find where to put the generated traits instead, but this still doesn't work cross-CU, so for now we ban. + report.error("Specialized traits may not be defined inside classes or traits (this would make them path-dependent which is not currently supported); they may be defined inside objects.", t.symbol.srcPos) + t.symbol.typeParams.foreach: par => + if par.paramVariance.isOneOf(Flags.Contravariant | Flags.Covariant) && Specialization.classSpecializedTypeParams(t.symbol).exists(t => t.typeSymbol == par) then + report.warning(VarianceInSpecializedTraitsLimitation(), par.srcPos) + specializations + case Typed(Apply(Select(New(anon),ctor),List()), t: TypeTree) if anon.symbol.isAnonymousClass => + (t.tpe, t.span) match { + case Specialization(spec) if spec.isFullySpecializedToTopClassesOrNothing => specializations.addErasedImplementation(spec) // We never inline into anonymous class instances (avoids cycles in inline trait inlining), so + // all anonymous class instances must have a non-anonymous class final representation as an $impl$ class. + case Specialization(spec) if spec.isSpecialized => specializations.addInterfaceAndImplementation(spec) + case _ => specializations + } + case Specialization(spec) => + if (spec.hasSpecializedParams) { + // Block Vec[?] and similar + spec.specializedTypeArgs.filter { + case t: TypeBoundsTree => true + case _ => false + }.foreach: tr => + report.error("Wildcard types may not be substituted for Specialized type parameters.", tr.srcPos) + } + if (spec.isSpecialized) { + specializations.addInterface(spec) + } else { + // Check foo[S: Specialized] <= Vec[S: Specialized] + spec.specializedTypeArgs.flatMap(arg => { // For each type we are using in a Specialized position + arg.tpe.widen.dealias.namedPartsWith(part => // Find all type params within that type that are not marked as Specialized so we can error + part.typeSymbol.isTypeParam && + (!(if part.typeSymbol.owner.isClass then part.typeSymbol.owner.primaryConstructor else part.typeSymbol.owner).paramSymss.flatten.exists( + d => d.info match { + case SpecializedEvidence(tpeArg) => + tpeArg.typeSymbol.isTypeParam && tpeArg.typeSymbol.name == part.name + case _ => false + } + )) + ) + }).foreach: tr => + if tr.denot.symbol.srcPos.span.exists then + report.error(s"${tr.typeSymbol} used in a Specialized position, so it must be marked as Specialized at its definition.", tr.denot.symbol.srcPos) + specializations + } + + case app @ Apply(_, _) => tpd.methPart(app) match { // class / object Bar extends Foo[Int](params) + case fun @ Select(New(tpt), init) if fun.symbol.isConstructor => tpd.allArgss(tree) match { + case typeArgs :: valueArgss => + val spec = Specialization(fun.symbol.owner, typeArgs, app.span) + if spec.isSpecialized then specializations.addInterface(spec) else specializations + case _ => specializations + } + case _ => specializations + } + + case _ => specializations + ) + }) +end DesugarSpecializedTraits + +object DesugarSpecializedTraits: + val name: String = "desugarSpecializedTraits" + val description: String = "Identifies traits having type parameters that have the Specialized annotation and generates corresponding specialized versions" // Replacement with specialized versions occurs in erasure. + + // TODO: Do we want to compress this more by adopting e.g. specializedTypeNames from scala 2? + // TODO: NameKind? + def canonicalName(tp: Type)(using Context): String = tp.dealias match + case AppliedType(tycon, args) => + canonicalName(tycon) + args.map(canonicalName).mkString("$_$") + case other => + other.typeSymbol.fullName.toString.replace('.', '$') + + + // TODO: What happens with this name generation if we have Vec[Vec[T]] for example? We potentially don't have an Ident + // TODO: Check what happens here when we have a case where the types being specialized into are user defined instead of primitives or type vars. + private def generateName(specialization: Specialization, suffix: String)(using Context) = + val name = (specialization.traitSymbol.name ++ suffix ++ "$").asTypeName ++ specialization.specializedTypeArgs.map(t => canonicalName(specType(t.tpe))).mkString(str.SPECIALIZED_TRAIT_TYPE_SEP) + if specialization.traitSymbol.owner.is(Flags.Package) then + name + else + FlatName(specialization.traitSymbol.owner.flatName.toTermName, name.toTermName).toSimpleName.toTypeName + + /*private[transform]*/ def newSpecializedTraitName(specialization: Specialization)(using Context): TypeName = + generateName(specialization, str.SPECIALIZED_TRAIT_SUFFIX) + + /*private[transform]*/ def newImplementationClassName(specialization: Specialization)(using Context): TypeName = + if specialization.isFullySpecializedToTopClassesOrNothing then + (specialization.traitSymbol.name ++ str.SPECIALIZED_TRAIT_IMPL_SUFFIX).asTypeName + else + generateName(specialization, str.SPECIALIZED_TRAIT_IMPL_SUFFIX) + + def isTopClass(s: Symbol)(using Context): Boolean = + (s eq defn.AnyClass) || (s eq defn.AnyValClass) || (s eq defn.ObjectClass) || (s eq defn.AnyRefAlias) + def isTopClassOrNothing(s: Symbol)(using Context): Boolean = (s eq defn.NothingClass) || isTopClass(s) + + def specType(tp: Type)(using Context): Type = + + def isSimpleClassType(s: Symbol): Boolean = + s.isClass && !s.is(Flags.Trait) && s.typeParams.isEmpty && s.isStatic + + tp.baseClasses.iterator.find(c => + isSimpleClassType(c) && (isTopClassOrNothing(c) || isTopClassOrNothing(c.asClass.superClass)) + ).map(_.typeRef).get + + def isSameErasureBucket(tp1: Type, tp2: Type)(using Context) = + val sp1 = specType(tp1) + val sp2 = specType(tp2) + (sp1 eq sp2) || isTopClassOrNothing(tp1.classSymbol) && isTopClassOrNothing(tp2.classSymbol) + +end DesugarSpecializedTraits + +/* + Stores the specializations we have found in the program and the symbols for the interface traits and implementation classes + that will replace them. We generate these symbols when we enter the specializations into the cache, via the functions + we store in genInterfaceSymbol and genImplementationSymbol. + + Model: Contains two levels: + - interface/implementation symbols we have found since the last installNewInterface/ImplementationSymbols call + (i.e. typically on this iteration) ("new") + - Those we found prior to that call, that were thus installed by it or previously + + Invariant: (newImplementationSymbols ∪ implementationSymbols) ⊆ (interfaceSymbols ∪ newInterfaceSymbols). +*/ + +object SpecializedTraitCache: + type SymbolMap = Map[Specialization, ClassSymbol] + type GenInterfaceSymbol = (Specialization, SpecializedTraitCache) => Context ?=> (ClassSymbol, SpecializedTraitCache) + type GenImplementationSymbol = (Specialization, Option[ClassSymbol]) => Context ?=> ClassSymbol + +// TODO: We don't need to share this between phases anymore and maybe we don't even need it at all. Can also rename. +class SpecializedTraitCache( + private val newInterfaceSymbols: SpecializedTraitCache.SymbolMap = Map.empty, + private val newImplementationSymbols: SpecializedTraitCache.SymbolMap = Map.empty, + private val interfaceSymbols: SpecializedTraitCache.SymbolMap = Map.empty, + private val implementationSymbols: SpecializedTraitCache.SymbolMap = Map.empty, + private val genInterfaceSymbol: SpecializedTraitCache.GenInterfaceSymbol, + private val genImplementationSymbol: SpecializedTraitCache.GenImplementationSymbol +): + + def copy( + newInterfaceSymbols: SpecializedTraitCache.SymbolMap = this.newInterfaceSymbols, + newImplementationSymbols: SpecializedTraitCache.SymbolMap = this.newImplementationSymbols, + interfaceSymbols: SpecializedTraitCache.SymbolMap = this.interfaceSymbols, + implementationSymbols: SpecializedTraitCache.SymbolMap = this.implementationSymbols, + genInterfaceSymbol: SpecializedTraitCache.GenInterfaceSymbol = this.genInterfaceSymbol, + genImplementationSymbol: SpecializedTraitCache.GenImplementationSymbol = this.genImplementationSymbol) + = SpecializedTraitCache(newInterfaceSymbols, newImplementationSymbols, interfaceSymbols, implementationSymbols, genInterfaceSymbol, genImplementationSymbol) + + def getInterfaceSymbol(spec: Specialization): Option[ClassSymbol] = newInterfaceSymbols.orElse(interfaceSymbols).lift(spec) + def getImplementationSymbol(spec: Specialization): Option[ClassSymbol] = newImplementationSymbols.orElse(implementationSymbols).lift(spec) + + def getNewInterfaceSymbols: List[(Specialization, ClassSymbol)] = newInterfaceSymbols.toList + def getNewImplementationSymbols: List[(Specialization, Option[ClassSymbol], ClassSymbol)] = newImplementationSymbols.map((k, v) => (k, getInterfaceSymbol(k), v)).toList + + def addInterface(spec: Specialization)(using Context): SpecializedTraitCache = + if (newInterfaceSymbols.contains(spec) || interfaceSymbols.contains(spec)) then + this + else + val (targetSymbol, resultingCache) = genInterfaceSymbol(spec, this) + resultingCache.copy(newInterfaceSymbols = resultingCache.newInterfaceSymbols + (spec -> targetSymbol)) + def addErasedImplementation(spec: Specialization)(using Context): SpecializedTraitCache = + val erased = Specialization(spec.traitSymbol, spec.mapSpecializedUnspecializedArgs(_ => TypeTree(defn.AnyClass.typeRef), spec.unspecializedTypeArgs), spec.span) + if (newImplementationSymbols.contains(erased) || implementationSymbols.contains(erased)) then + this + else + this.copy(newImplementationSymbols = this.newImplementationSymbols + (erased -> genImplementationSymbol(erased, this.getInterfaceSymbol(erased)))) + def addInterfaceAndImplementation(spec: Specialization)(using Context): SpecializedTraitCache = + if (newImplementationSymbols.contains(spec) || implementationSymbols.contains(spec)) then + this + else + val withInterface = addInterface(spec) + withInterface.copy(newImplementationSymbols = withInterface.newImplementationSymbols + (spec -> genImplementationSymbol(spec, withInterface.getInterfaceSymbol(spec)))) + + def installNewInterfaceSymbols = + this.copy( + newInterfaceSymbols = Map.empty, + interfaceSymbols = interfaceSymbols ++ newInterfaceSymbols) + + def installNewImplementationSymbols = + this.copy( + newImplementationSymbols = Map.empty, + implementationSymbols = implementationSymbols ++ newImplementationSymbols) + +end SpecializedTraitCache + +/* Represents an application traitSymbol[typeArguments] */ +class Specialization(val traitSymbol: Symbol, val typeArguments: List[Tree], val span: Span)(using Context): // TODO: Can we get away with List[Type] + val specializedTypeParams: List[Type] = Specialization.classSpecializedTypeParams(traitSymbol) // Type parameters marked with Specialized + + // private val specializedTypeParamsSet = specializedTypeParams.toSet // TODO: We can bring this back if we manage to get the =:= type hashing but it's really not a big deal given the expected number of type parameters. + private val paramToArgList = traitSymbol.typeParams.map(_.typeRef.asInstanceOf[Type]).zip(typeArguments) + + val unspecializedTypeParams: List[Type] = paramToArgList.filterNot((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)).map(_._1) // Type parameters not marked with Specialized + val specializedTypeArgs: List[Tree] = paramToArgList.filter((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)).map(_._2) // Type arguments provided to parameters that are marked with Specialized at their definition + val unspecializedTypeArgs: List[Tree] = paramToArgList.filterNot((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)).map(_._2) // Type arguments provided to parameters that are not marked with Specialized at their definition + + val specializedTypeParamsToTypeArgumentsMap: Map[Type, Tree] = paramToArgList.toMap.filter((k, v) => specializedTypeParams.exists(_ =:= k)).view.mapValues(tr => TypeTree(specType(tr.tpe))).toMap // TODO: Maybe not efficient + val specialization: List[Tree] = traitSymbol.typeParams.map(_.typeRef).map(specializedTypeParamsToTypeArgumentsMap.applyOrElse(_, TypeTree(_))) // TODO: Don't really like this name + + def constructorTypeParams: List[Type] = traitSymbol.primaryConstructor.rawParamss.head.map(_.typeRef) + def unspecializedConstructorParams: List[Symbol] = traitSymbol.primaryConstructor.rawParamss.head.zip(traitSymbol.typeParams).filterNot((constrParam, typeParam) => specializedTypeParams.exists(_ =:= typeParam.typeRef)).map((constrParam, typeParam) => constrParam) + def specializedConstructorParamToArgumentTypeMap: Map[Type, Type] = + traitSymbol.primaryConstructor.rawParamss.head.map(_.typeRef).zip(paramToArgList).filter((constrParam, paramArg) => specializedTypeParams.exists(_ =:= paramArg._1)).map((constrParam, paramArg) => (constrParam, specType(paramArg._2.tpe))).toMap + + val hasSpecializedParams: Boolean = specializedTypeParams.nonEmpty + + def mapSpecializedUnspecializedArgs(spec: Tree => Tree, unspec: List[Tree]): List[Tree] = paramToArgList.foldLeft((List.empty[Tree], unspec))((resUnspec, paramArg) => ((resUnspec, paramArg): @unchecked) match { + case ((result, unspec), (param, arg)) if specializedTypeParams.exists(_ =:= param) => (spec(arg) :: result, unspec) + case ((result, head :: rest), (param, arg)) => (head :: result, rest) + })._1.reverse + + /* If inline trait Foo[T: Specialized] has a method taking another Foo[T] there's no point specializing the reference + since the resulting sp$T$ would be the same as the starting trait. Also A[Object] specializes to A. */ + def isSpecialized: Boolean = + hasSpecializedParams && specializedTypeArgs.exists(tree => !isTopClassOrNothing(specType(tree.tpe).classSymbol)) + // Only works before erasure. + def isFullySpecialized: Boolean = + !specializedTypeArgs.exists(_.tpe.existsPart(part => (part.typeSymbol.isTypeParam))) + def isFullySpecializedToTopClassesOrNothing: Boolean = + hasSpecializedParams && isFullySpecialized && specializedTypeArgs.forall(tr => isTopClassOrNothing(specType(tr.tpe).classSymbol)) + + // Note: We only care about the specialized arguments for equality; a specialization of Vec[A: Specialized, B] with B = Int and one + // with B = String can be considered to be the same as they use the same specialized trait + // TODO: I don't really like this logic being in Specialization because they are really different + // We should really put that logic in the SpecializedTraitCache because it's at that point that we treat them as the same. + override def equals(obj: Any): Boolean = + obj.isInstanceOf[Specialization] && obj.asInstanceOf[Specialization].traitSymbol == traitSymbol + && specializedTypeArgs.zip(obj.asInstanceOf[Specialization].specializedTypeArgs).forall((a1, a2) => specType(a1.tpe) == specType(a2.tpe)) + + override def hashCode(): Int = + (traitSymbol, specializedTypeArgs.map(tr => specType(tr.tpe))).hashCode() + + override def toString(): String = + s"Specialization(${traitSymbol}, ${typeArguments}, ${span})" +end Specialization + +/* Represents an application methodSymbol[typeArguments](termArgs1)(termArgs2) etc */ +class MethodSpecialization(val methodSymbol: Symbol, val typeArgss: List[List[Tree]])(using Context): + val specializedTypeParams: List[Type] = methodSymbol.paramSymss.flatten.collect(_.info match { case SpecializedEvidence(typeVar) => typeVar }) // Type parameters marked with Specialized + private val paramToArgList = + methodSymbol.paramSymss.filter(l => l.nonEmpty && l.head.is(Flags.TypeParam)).zip(typeArgss).map( + (params, args) => params.map(_.typeRef.asInstanceOf[Type]).zip(args) + ).flatten + + // TODO: Can we share these? General Specialization + Method + Trait + val specializedTypeArgs: List[Tree] = paramToArgList.filter((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)).map(_._2) // Type arguments provided to parameters that are marked with Specialized at their definition + + val hasSpecializedParams: Boolean = specializedTypeParams.nonEmpty + + def isSpecialized: Boolean = + methodSymbol.isSpecializedMethod && hasSpecializedParams && specializedTypeArgs.exists(tree => !isTopClass(specType(tree.tpe).classSymbol)) + def isFullySpecialized: Boolean = + !specializedTypeArgs.exists(_.tpe.existsPart(part => (part.typeSymbol.isTypeParam))) + def isFullySpecializedToTopClassesOrNothing: Boolean = + methodSymbol.isSpecializedMethod && hasSpecializedParams && isFullySpecialized && specializedTypeArgs.forall(tr => isTopClassOrNothing(specType(tr.tpe).classSymbol)) +end MethodSpecialization + +// TODO: If we can get this in Specialization when we do inheritance that would be great. +object SpecializedEvidence { + def unapply(tpe: Type)(using Context): Option[Type] = tpe match { + case AppliedType(tycon, List(tpeArg)) if (tycon =:= ctx.definitions.SpecializedClass.typeRef && tpeArg.typeSymbol.isTypeParam) => Some(tpeArg) + case _ => None + } +} + +object Specialization: + + def unapply(tpt: Tree)(using Context): Option[Specialization] = tpt match { + case AppliedTypeTree(specializedTrait: Ident, concreteTypeTrees: List[Tree]) => Some(Specialization(specializedTrait.denot.symbol, concreteTypeTrees, tpt.span)) + case t: TypeTree => Specialization.unapply(t.tpe, t.span) + case _ => None + } + + def unapply(typeSpan: (Type, Span))(using Context): Option[Specialization] = typeSpan match { + case (AppliedType(tycon: Type, args: List[Type]), span) => Some(Specialization(tycon.typeSymbol, args.map(TypeTree(_)), span)) + case _ => None + } + + def unapply(tpe: Type)(using Context): Option[Specialization] = tpe match { + case AppliedType(tycon: Type, args: List[Type]) => Some(Specialization(tycon.typeSymbol, args.map(TypeTree(_)), NoSpan)) + case _ => None + } + + def classSpecializedTypeParams(classSym: Symbol)(using Context): List[Type] = classSym.unforcedDecls.implicitDecls.collect(_.info match { case SpecializedEvidence(typeVar) => typeVar }) + def methodSpecializedTypeParams(methodSym: Symbol)(using Context): List[Type] = methodSym.paramSymss.flatten.collect(_.info match { case SpecializedEvidence(typeVar) => typeVar }) + + // TODO: These methods are used in other phases; probably move them to the phase object? + def anonymousClassIsSpecialized(tree: Tree)(using Context) = + tree match { + case TypeDef(anon, Template(_, parentCalls: List[Tree], _, _)) => + parentCalls match { + case _ :+ Apply(Apply(t, ctorArgs), ev) => // extends Object, parents of spec trait, spec trait + val spec = Specialization.unapply(t.tpe.resultType.resultType, t.span) + spec.get.hasSpecializedParams + case _ => false + } + case _ => false + } + + def isSpecializedTrait(sym: Symbol)(using Context) = sym.isClass && sym.isAllOf(InlineTrait) && classSpecializedTypeParams(sym).nonEmpty + def isSpecializedMethod(sym: Symbol)(using Context) = sym.isAllOf(InlineMethod) && methodSpecializedTypeParams(sym).nonEmpty + def traitParamIsSpecialized(traitSym: Symbol, tParam: Symbol)(using Context) = classSpecializedTypeParams(traitSym).exists(tp => tp.typeSymbol eq tParam) +end Specialization + +object MethodSpecialization: + def unapply(tree: Tree)(using Context) = tree match { + case app: Apply => + val methSym = tpd.methPart(app).symbol + if methSym.is(Flags.Method) then + Some(MethodSpecialization(methSym, tpd.typeArgss(app))) + else + None + case _ => None + } +end MethodSpecialization + +class AnonymousClassInstance( + val srcPos: SrcPos, + val symbol: Symbol, + val body: List[Tree], + val parentCalls: List[Tree], + val ctor: Name, + val typeTree: TypeTree +) + +object AnonymousClassInstance: + def unapply(tree: Tree)(using Context) = tree match { + case Block(List(an@TypeDef(anon, tmpl@Template(_, parentCalls: List[Tree], _, _))), + Typed(Apply(Select(New(anon1),ctor), _), t: TypeTree)) if anon1.symbol.isAnonymousClass && (anon1.symbol eq an.symbol) => + Some(AnonymousClassInstance(an.srcPos, an.symbol, tmpl.body, parentCalls, ctor, t)) + + // Coverage testing creates this extra case + case Block(List(an@TypeDef(anon, tmpl@Template(_, parentCalls: List[Tree], _, _))), + Typed(Block(bindings, Apply(Select(New(anon1),ctor), _)), t: TypeTree)) if anon1.symbol.isAnonymousClass && (anon1.symbol eq an.symbol) => + Some(AnonymousClassInstance(an.srcPos, an.symbol, tmpl.body, parentCalls, ctor, t)) + case _ => None + } +end AnonymousClassInstance + +class SpecializedTraitState: + var specializedTraitCache: Option[SpecializedTraitCache] = None +end SpecializedTraitState + + +// Need to somehow make my naming a lot more consistent as well. +// figure out why we generate the T version. +// Try to see if we can do with only types and not trees +// Synthesise Specialized instances so that people can't do stupid stuff like Specialized[Array[T]]. type x = Specialized[Array[Array[Int]]] +// Set the Synthetic flags somewhere +// Cache / only generate once instead of multiple times. +// Ideally standardise on either specialization or specializationMap + +// TODO: Need to try with a bigger project with multiple packages later on to see if we get the behaviour that we are expecting to get in terms of the classes that we generate. + +// TODO: need to test with explicit evidence / our own custom type classes + +// In the case of foo[S](a: Vec[S, Int, Int, Int, Int]) I think we ideally do want this because we should be able to get speed gains by accessing the specialized members + +// TODO: Only specialize if there is some material increase in specialization - I think only if at least one new parameter gets fully specialized +// Maybe it is better to not allow partial specializations -- we can think about that. + +// TODO: Don't synthesize specialized instances for random generic types probably - as Hamza said we want to be able to control the specialization diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 1e7a03bcd3f4..fb47df9d6885 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -37,6 +37,7 @@ import core.Mode import util.Property import reporting.* import scala.annotation.tailrec +import dotty.tools.dotc.ast.tpd.* class Erasure extends Phase with DenotTransformer { @@ -740,6 +741,20 @@ object Erasure { adaptIfSuper(qual) match case qual1: Super => select(qual1, sym) + case qual1 if owner.isInlineTrait && + (qual1.tpe.widenDealias.classSymbol ne sym.owner) && + qual1.tpe.widenDealias.classSymbol.derivesFrom(sym.owner) => + + // If A is an inline trait and A.foo was inlined into B, references to b.foo (val b = B()) will still + // point to A.foo until now. We want them to point to B.foo so we get the benefit of specialization. + // We fix that here rather than in a separate phase because + // it needs to happen coordinated with erasure of Specialized traits, so that: + // a) we see the erased A$sp$Int traits and can point at their members + // b) we make the replacement before boxing in case A.foo is typed with T and B.foo specializes this to e.g. Int + // Otherwise we will end up with Int.unbox(A.foo) instead of directly B.foo which won't typecheck. + val specializedInterfaceSym = qual1.tpe.widenDealias.classSymbol.asClass + val newSym = inContext(preErasureCtx) { sym.overridingSymbol(specializedInterfaceSym) } + qual1.select(newSym) case qual1 if !isJvmAccessible(qual1.tpe.typeSymbol) || !qual1.tpe.derivesFrom(sym.owner) => val castTarget = // Avoid inaccessible cast targets, see i8661 @@ -789,6 +804,35 @@ object Erasure { } } + /* Erase anonymous instances of specialized traits to $impl$ classes */ + override def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = tree.asInstanceOf[Block] match + case AnonymousClassInstance(anon) => + inContext(preErasureCtx) { + Specialization.unapply(anon.typeTree.tpe, anon.typeTree.span).flatMap(spec => { + anon.parentCalls match { + case (obj :: parentsOfSpecTrait) if (spec.isSpecialized || spec.isFullySpecializedToTopClassesOrNothing) && (obj.symbol.owner == ctx.definitions.ObjectClass) && (parentsOfSpecTrait.forall(x => spec.traitSymbol.asClass.baseClasses.exists(p => p == x.symbol.owner))) => + val app: Tree = parentsOfSpecTrait.find(p => p.symbol.owner == spec.traitSymbol).get + assert(app.isInstanceOf[Apply]) // At the very least we pass the Specialized instance. + val targetImplName = DesugarSpecializedTraits.newImplementationClassName(spec) + val implClass = spec.traitSymbol.enclosingPackageClass.info.decls.lookup(targetImplName) + assert(implClass.exists && implClass.isClass) + val erased = inContext(ctx.withSource(anon.typeTree.source)) { + Typed( + Select(New(ref(implClass)), anon.ctor) + .appliedToTypeTrees(spec.unspecializedTypeArgs) + .appliedToArgss(tpd.allArgss(app).tail.nestedMap(_.changeNonLocalOwners(anon.symbol.owner))) // Skip the type params which are not needed + , anon.typeTree) + }.withSpan(anon.typeTree.span) + Some(erased) + case _ => None + }}) + } match { + case Some(erased) => typedTyped(erased, anon.typeTree.tpe) + case None => super.typedBlock(tree, pt) + } + case _ => super.typedBlock(tree, pt) + + override def typedBind(tree: untpd.Bind, pt: Type)(using Context): Bind = atPhase(erasurePhase): checkBind(promote(tree)) @@ -1015,7 +1059,41 @@ object Erasure { EmptyTree override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree = - val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef, cls): @unchecked + // drop Foo[Int] leading to duplicate Foo$sp$Int + val TypeDef(_, implInit: Template) = cdef: @unchecked + + // Match corresponding class info erasure in TypeErasure::apply ClassInfo case + val cdef1 = + val oldParents = implInit.asInstanceOf[Template].parents + val superCtxNoSpec = disallowSpecializedCtx(using ctx.superCallContext) + val newParents = + if cls.isSpecializedTraitInterface then // {source: Bar, Foo both specialized traits} inline trait Bar$sp$Int extends Object, Bar, Foo$sp$Int + val (obj :: originalTrait :: inheritedParents) = oldParents : @unchecked + obj :: typedType(originalTrait)(using superCtxNoSpec) :: inheritedParents + else if cls.isSpecializedTraitImplementationClass && !cls.isRawSpecializedTraitImplementationClass then // {source: Bar, Foo both specialized traits} class Bar$impl$Int extends Object, Bar$sp$Int, Bar(10) + val (objectParent :: traitSpParent :: originalTraitSpecializedParent :: Nil) = oldParents : @unchecked + val newParent = originalTraitSpecializedParent match { + case _: untpd.Apply => typedExpr(originalTraitSpecializedParent)(using superCtxNoSpec) + case _ => typedType(originalTraitSpecializedParent)(using superCtxNoSpec) + } + objectParent :: traitSpParent :: newParent :: Nil + else + inContext(preErasureCtx) { + val extraSpTraits = oldParents.filter(p => p.symbol.isPrimaryConstructor && p.symbol.owner.isSpecializedTrait).map(p => p.tpe.resultType) + + // {source: class Bar extends Foo[Int](10) with Baz[Int](10)} + // class Bar extends Object, Foo(10), Bar(10), Foo$sp$Int, Bar$sp$Int + oldParents.map { tp => + if tp.symbol.isPrimaryConstructor && tp.symbol.owner.isSpecializedTrait then + typedExpr(tp)(using superCtxNoSpec) + else + tp + } ::: extraSpTraits.map(sym => TypeTree(sym)) + } + + cpy.TypeDef(cdef.asInstanceOf[TypeDef])(rhs = cpy.Template(implInit.asInstanceOf[Template])(parents = newParents)) + + val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef1, 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 => diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 81cc73c26ed6..0e0d211faf35 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -22,6 +22,7 @@ import inlines.Inlines.inInlineMethod import util.Property import inlines.Inlines import reporting.InlinedAnonClassWarning +import dotty.tools.dotc.transform.Specialization.anonymousClassIsSpecialized object FirstTransform { val name: String = "firstTransform" @@ -210,7 +211,9 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => } override def transformTypeDef(tree: TypeDef)(using Context): Tree = - if tree.symbol.isAnonymousClass && Inlines.inInlineMethod then + if tree.symbol.isAnonymousClass && Inlines.inInlineMethod && + !anonymousClassIsSpecialized(tree) + then report.warning(InlinedAnonClassWarning(), tree.symbol.sourcePos) tree diff --git a/compiler/src/dotty/tools/dotc/transform/Getters.scala b/compiler/src/dotty/tools/dotc/transform/Getters.scala index 11adf4da83d5..c63d1384a9e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Getters.scala +++ b/compiler/src/dotty/tools/dotc/transform/Getters.scala @@ -62,6 +62,8 @@ class Getters extends MiniPhase with SymTransformer { thisPhase => override def description: String = Getters.description + override def changesMembers: Boolean = true // TODO: Is this ok? I mean it does call enteredAfter via ensureSetter and that does change members. + override def transformSym(d: SymDenotation)(using Context): SymDenotation = { def noGetterNeeded = d.isOneOf(NoGetterNeededFlags) || diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index bb131ea0db0e..227d37e361a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -81,7 +81,7 @@ class Inlining extends MacroTransform, IdentityDenotTransformer { new TreeTraverser { def traverse(tree: Tree)(using Context): Unit = tree match - case tree: RefTree if !Inlines.inInlineMethod && StagingLevel.level == 0 => + case tree: RefTree if !Inlines.inInlineContext && StagingLevel.level == 0 => assert(!tree.symbol.isInlineMethod, tree.show) case _ => traverseChildren(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 3f0b1a4783ae..05c9a910018b 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -94,7 +94,7 @@ object Mixin { * def x_=(y: T) = () * * 4.5 (done in `mixinForwarders`) For every method - * ` def f[Ts](ps1)...(psN): U` in M` that needs to be disambiguated: + * ` def f[Ts](ps1)...(psN): U` in M that needs to be disambiguated: * * def f[Ts](ps1)...(psN): U = super[M].f[Ts](ps1)...(psN) * @@ -140,6 +140,24 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => override def transformSym(sym: SymDenotation)(using Context): SymDenotation = def ownerIsTrait: Boolean = was(sym.owner, Trait, butNot = JavaDefined) + // // See: tests/run/inline-trait-param-shadows-parent.scala and tests/run/inline-trait-param-shadows-parent-indirect.scala + // // We need this even though we also have "if mixin.isInlineTrait then return Nil" because getters can come + // // from traits that inherit inline traits as well as from inline traits themselves. + // // TODO: Do we not only want to do this if we get the symbol from somewhere else in the child trait as well? + // // i.e. need to know that the trait is mixed in either directly or indirectly as another parent as well. + // // Maybe just say that ordinary traits cannot inherit from inline traits? That would probably fix it. + // def isFromInlineTraitInlining(getter: Symbol): Boolean = + // val y = mixin.parentSyms + + // val x = mixin.parentSyms.map( + // parentSym => parentSym.info.decls//.exists(d => d.name == getter.name || getter.name == d.name.expandedName(parentSym)) + // ) + // mixin.parentSyms.exists( + // parentSym => parentSym.isInlineTrait && parentSym.info.decls.exists(d => { + // d.name == getter.name) + // ) + + if (sym.is(Accessor, butNot = Deferred) && ownerIsTrait) { val sym1 = if (sym.is(Lazy) || sym.symbol.isConstExprFinalVal) sym @@ -186,6 +204,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => sym.isGetter && !wasOneOf(sym, DeferredOrLazy | ParamAccessor) && atPhase(thisPhase) { !sym.setter.exists } && !sym.isConstExprFinalVal + && !sym.owner.isInlineTrait private def makeTraitSetter(getter: TermSymbol)(using Context): Symbol = getter.copy( @@ -271,19 +290,36 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => cls.srcPos) EmptyTree + // See: tests/run/inline-trait-param-shadows-parent.scala and tests/run/inline-trait-param-shadows-parent-indirect.scala + // We need this even though we also have "if mixin.isInlineTrait then return Nil" because getters can come + // from traits that inherit inline traits as well as from inline traits themselves. + // TODO: Do we not only want to do this if we get the symbol from somewhere else in the child trait as well? + // i.e. need to know that the trait is mixed in either directly or indirectly as another parent as well. + // Maybe just say that ordinary traits cannot inherit from inline traits? That would probably fix it. + def isFromInlineTraitInlining(getter: Symbol): Boolean = + val y = mixin.parentSyms + + val x = mixin.parentSyms.map( + parentSym => parentSym.info.decls//.exists(d => d.name == getter.name || getter.name == d.name.expandedName(parentSym)) + ) + mixin.parentSyms.exists( + parentSym => parentSym.isInlineTrait && parentSym.info.decls.exists(d => d.name == getter.name) + ) + for getter <- mixin.info.decls.toList if getter.isGetter && !wasOneOf(getter, Deferred) && !getter.isConstExprFinalVal + && !isFromInlineTraitInlining(getter) yield if (isInImplementingClass(getter) || getter.name.is(ExpandedName)) { val rhs = if (wasOneOf(getter, ParamAccessor)) nextArgument() - else if (getter.is(Lazy, butNot = Module)) + else if (!mixin.isInlineTrait && getter.is(Lazy, butNot = Module)) transformFollowing(superRef(getter).appliedToNone) - else if (getter.is(Module)) + else if (!mixin.isInlineTrait && getter.is(Module)) if ctx.settings.scalajs.value && getter.moduleClass.isJSType then if getter.is(Scala2x) then report.error( @@ -295,24 +331,28 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => New(getter.info.resultType, List(This(cls))) else Underscore(getter.info.resultType) - // transformFollowing call is needed to make memoize & lazy vals run - val forwarder = mkForwarderSym(getter.asTerm) - // Store the unerased form for generic signature use later, - // but only if it's not private (which we must check at erasure time, as here we've removed that flag already), - // since otherwise it might refer to private classes - if atPhase(erasurePhase) { !getter.is(Private) } then - mixinGenericInfos(forwarder) = atPhase(erasurePhase) { cls.thisType.memberInfo(getter) } - transformFollowing(DefDef(forwarder, rhs)) + + if (!mixin.isInlineTrait) then + // transformFollowing call is needed to make memoize & lazy vals run + val forwarder = mkForwarderSym(getter.asTerm) + // Store the unerased form for generic signature use later, + // but only if it's not private (which we must check at erasure time, as here we've removed that flag already), + // since otherwise it might refer to private classes + if atPhase(erasurePhase) { !getter.is(Private) } then + mixinGenericInfos(forwarder) = atPhase(erasurePhase) { cls.thisType.memberInfo(getter) } + transformFollowing(DefDef(forwarder, rhs)) + else + EmptyTree + } else if wasOneOf(getter, ParamAccessor) then - // mixin parameter field is defined by an override; evaluate the argument and throw it away - nextArgument() + if (mixin.isInlineTrait) then {nextArgument(); EmptyTree} else nextArgument() else EmptyTree } def setters(mixin: ClassSymbol): List[Tree] = val mixinSetters = mixin.info.decls.filter { sym => - sym.isSetter && (!wasOneOf(sym, Deferred) || sym.name.is(TraitSetterName)) + sym.isSetter && (!wasOneOf(sym, Deferred) || sym.name.is(TraitSetterName)) && !sym.owner.isInlineTrait } mixinSetters.map(setter => { val copied = transformFollowing(DefDef(mkForwarderSym(setter.asTerm), unitLiteral.withSpan(cls.span))) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 1034dbfba940..331a4c77a96b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -80,9 +80,9 @@ class PickleQuotes extends MacroTransform { override def checkPostCondition(tree: Tree)(using Context): Unit = tree match case tree: Quote => - assert(Inlines.inInlineMethod) + assert(Inlines.inInlineContext) case tree: Splice => - assert(Inlines.inInlineMethod) + assert(Inlines.inInlineContext) case _ => override protected def run(using Context): Unit = diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 2fb9aca1feff..fa21c89742df 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -26,6 +26,7 @@ import cc.* import dotty.tools.dotc.transform.MacroAnnotations.hasMacroAnnotation import dotty.tools.dotc.core.NameKinds.DefaultGetterName import ast.TreeInfo +import dotty.tools.dotc.core.NameKinds.ContextBoundParamName import dotty.tools.dotc.cc.derivedFunctionOrMethod object PostTyper { @@ -264,6 +265,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => tree match case tree: ValOrDefDef if !sym.is(Synthetic) => checkInferredWellFormed(tree.tpt) + if tree.symbol.owner.isInlineTrait then checkInlTraitPrivateMemberIsLocal(tree) if sym.is(Method) then if sym.isSetter then sym.keepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot)) @@ -310,6 +312,9 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => => Checking.checkAppliedTypesIn(tree) case _ => + private def checkInlTraitPrivateMemberIsLocal(tree: Tree)(using Context): Unit = + if tree.symbol.owner.isInlineTrait && tree.symbol.isAllOf(Private, butNot = Local) then + report.error(em"implementation restriction: inline traits cannot have non-local private members. This also means no retained inline methods.", tree.srcPos) private def transformSelect(tree: Select, targs: List[Tree])(using Context): Tree = { val qual = tree.qualifier @@ -573,6 +578,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => if tree.isType then checkNotPackage(tree) else + if tree.symbol == defn.SpecializedModule && (ctx.owner ne defn.SpecializedModule.moduleClass) then + report.error(IllegalUseOfSpecialized(), tree.srcPos) registerNeedsInlining(tree) val tree1 = checkUsableAsValue(tree) tree1.tpe match { @@ -675,6 +682,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => val tree1 = cpy.DefDef(tree)(tpt = explicifyTpt(tree)) processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) case tree: TypeDef => + if tree.symbol.isInlineTrait then + ctx.compilationUnit.needsInlining = true // Transform inner classes to traits + if tree.rhs.tpe.existsPart(t => t.typeSymbol == defn.SpecializedClass.asType) && (tree.symbol ne defn.SpecializedClass) then + report.error(IllegalUseOfSpecialized(), tree.srcPos) registerIfHasMacroAnnotations(tree) val sym = tree.symbol if (sym.isClass) @@ -686,6 +697,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => tree.rhs match case impl: Template => for parent <- impl.parents do + if Inlines.symbolFromParent(parent).isInlineTrait then + ctx.compilationUnit.needsInlining = true Checking.checkTraitInheritance(parent.tpe.classSymbol, sym.asClass, parent.srcPos) // Constructor parameters are in scope when typing a parent. // While they can safely appear in a parent tree, to preserve @@ -745,6 +758,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => else if (tree.tpt.symbol == defn.orType) () // nothing to do else + if tree.tpt.symbol == defn.SpecializedClass && !(ctx.owner.name.is(ContextBoundParamName) || ctx.owner.ownersIterator.contains(defn.SpecializedModule_apply)) then + report.error(IllegalUseOfSpecialized(), tree.srcPos) Checking.checkAppliedType(tree) super.transform(tree) case SingletonTypeTree(ref) => diff --git a/compiler/src/dotty/tools/dotc/transform/PruneInlineTraits.scala b/compiler/src/dotty/tools/dotc/transform/PruneInlineTraits.scala new file mode 100644 index 000000000000..db191c4080ed --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PruneInlineTraits.scala @@ -0,0 +1,64 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts._ +import DenotTransformers.SymTransformer +import Flags._ +import SymDenotations._ +import Symbols._ +import MegaPhase.MiniPhase +import ast.tpd +import dotty.tools.dotc.core.StdNames.str + +class PruneInlineTraits extends MiniPhase with SymTransformer { thisTransform => + import tpd._ + import PruneInlineTraits._ + + override def phaseName: String = PruneInlineTraits.name + + override def description: String = PruneInlineTraits.description + override def runsAfter: Set[String] = Set(PruneSpecializedMethods.name) + + override def transformSym(sym: SymDenotation)(using Context): SymDenotation = + if isEraseable(sym) then sym.copySymDenotation(initFlags = sym.flags | Deferred) + else if sym.isInlineTrait then + val clsInfo = sym.asClass.classInfo + val clsInfo2 = clsInfo.derivedClassInfo(decls = + clsInfo.decls.filteredScope(!isDeletable(_)) + ) + sym.copySymDenotation(initFlags = sym.flags | PureInterface | NoInits, info = clsInfo2) + else sym + + override def transformTemplate(tree: Template)(using Context): Tree = + cpy.Template(tree)(body = tree.body.flatMap({ + case stmt: ValDef if isEraseable(stmt.symbol) => Some(cpy.ValDef(stmt)(rhs = EmptyTree)) + case stmt: DefDef if isEraseable(stmt.symbol) => Some(cpy.DefDef(stmt)(rhs = EmptyTree)) + case stmt: (ValDef | DefDef) if isDeletable(stmt.symbol) => None + case stmt => Some(stmt) + })) + + private def isEraseable(sym: SymDenotation)(using Context): Boolean = + !sym.isType + && !sym.isConstructor + && !sym.is(Param) + && !sym.is(ParamAccessor) + && !sym.is(Local) + && !sym.isLocalDummy + && sym.exists + && sym.owner.isInlineTrait + + private def isDeletable(sym: SymDenotation)(using Context): Boolean = + !sym.isType + && sym.owner.isInlineTrait + && (sym.is(Local) || (sym.is(Inline) && !sym.isRetainedInline)) // isRetainedInline not an issue while we block non-local privates as retainedBody is non local private. + && !sym.is(Param) + && !sym.is(ParamAccessor) +} + +object PruneInlineTraits { + import tpd._ + + val name: String = "pruneInlineTraits" + val description: String = "drop rhs definitions in inline traits" +} diff --git a/compiler/src/dotty/tools/dotc/transform/PruneSpecializedMethods.scala b/compiler/src/dotty/tools/dotc/transform/PruneSpecializedMethods.scala new file mode 100644 index 000000000000..9d47ce0cd928 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PruneSpecializedMethods.scala @@ -0,0 +1,45 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts._ +import DenotTransformers.InfoTransformer +import Flags._ +import SymDenotations._ +import Symbols._ +import MegaPhase.MiniPhase +import ast.tpd +import dotty.tools.dotc.core.StdNames.str +import dotty.tools.dotc.core.Types._ + +class PruneSpecializedMethods extends MiniPhase with InfoTransformer { thisTransform => + import tpd._ + import PruneSpecializedMethods._ + + override def phaseName: String = PruneSpecializedMethods.name + + override def description: String = PruneSpecializedMethods.description + + override def transformInfo(tp: Type, sym: Symbol)(using Context) = tp match { + case clsInfo: ClassInfo if sym.isClass && !sym.is(Package) => + clsInfo.derivedClassInfo(decls = + clsInfo.decls.filteredScope(!isDeletable(_)) + ) + case _ => tp + } + + override def transformTemplate(tree: Template)(using Context): Tree = + cpy.Template(tree)(body = tree.body.flatMap({ + case stmt: DefDef if isDeletable(stmt.symbol) => None + case stmt => Some(stmt) + })) + + private def isDeletable(sym: SymDenotation)(using Context): Boolean = sym.isSpecializedMethod +} + +object PruneSpecializedMethods { + import tpd._ + + val name: String = "pruneSpecializedMethods" + val description: String = "drop specialized methods which have already been inlined; we can't wait until erasure because they can be broken by pruneInlineTraits removing members from the specialized traits they instantiate" +} diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeInlineTraits.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeInlineTraits.scala new file mode 100644 index 000000000000..0697bf726d44 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeInlineTraits.scala @@ -0,0 +1,102 @@ +package dotty.tools.dotc +package transform + + +import core._ +import Flags._ +import Contexts._ +import Symbols._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.inlines.Inlines +import dotty.tools.dotc.ast.TreeMapWithImplicits +import dotty.tools.dotc.core.DenotTransformers.SymTransformer +import dotty.tools.dotc.staging.StagingLevel +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.core.StdNames.{str, nme} +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Names.{Name, TermName} + +import scala.collection.mutable +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.inlines.Inlines.InlineTraitState +import dotty.tools.dotc.ast.TreeTypeMap + +class SpecializeInlineTraits extends MiniPhase { + + import tpd._ + + override def phaseName: String = SpecializeInlineTraits.name + + override def description: String = SpecializeInlineTraits.description + + override def changesMembers: Boolean = true + + override def changesParents: Boolean = true + + override def prepareForUnit(tree: Tree)(using Context): Context = + ctx.fresh.setInlineTraitState(ctx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.InlineTraits)) + + private val seen = mutable.HashSet[Symbol]() + + private def inlineInlineTraitsIfNew(tree: Tree)(using Context) = + if !seen.contains(tree.symbol) then + seen.add(tree.symbol) + Inlines.inlineParentInlineTraits(tree) + else + tree + + override def transformTypeDef(tree: TypeDef)(using Context): Tree = + new TreeMapWithPreciseStatContexts { // We need to inline recursively because inlining may create further opportunities for inlining. + // Notably this does limit the composition potential of this miniphase. + // We use seen to make sure we don't inline into the same child class multiple times + override def transform(tree: Tree)(using Context): Tree = + tree match { + case tree: TypeDef if tree.symbol.isInlineTrait => + val tree1 = Inlines.checkAndTransformInlineTrait(tree) + val tree2 = if Inlines.needsInlining(tree1) then inlineInlineTraitsIfNew(tree1) else tree1 + super.transform(tree2) // We may need to inline inline traits into the bodies of methods defined inside inline traits. + case tree: TypeDef if Inlines.needsInlining(tree) => + if tree.symbol.isAllOf(Trait, butNot = Inline) then + val problemParents = tree.symbol.info.parents.filter( + p => p.classSymbol.isInlineTrait + && p.classSymbol.primaryConstructor.paramSymss.exists(paramList => paramList.nonEmpty && paramList.head.isTerm) + ) + problemParents.foreach( p => + val message = if p.typeSymbol.isSpecializedTrait then "Specialized traits may not be extended by ordinary traits. They may only be extended by classes, objects or inline/specialized traits." + else s"Only parameterless inline traits may be extended by ordinary traits. Make ${tree.symbol} inline or remove inline ${p.typeSymbol}'s parameter list." + + report.error(message, tree.srcPos) + ) + val tree1 = + if tree.symbol.isInlineTrait then + inlineInlineTraitsIfNew(Inlines.checkAndTransformInlineTrait(tree)) + else inlineInlineTraitsIfNew(tree) + super.transform(tree1) + case t => super.transform(t) + } + }.transform(tree) + + override def checkPostCondition(tree: Tree)(using Context): Unit = + tree match { + // TODO check that things are inlined properly + case _ => + } + + private object ConcreteParentStripper extends TreeAccumulator[Tree] { + def apply(tree: Tree)(using Context): Tree = apply(tree, tree) + + override def apply(x: Tree, tree: Tree)(using Context): Tree = tree match { + case ident: Ident => ident + case tpt: TypeTree => tpt + case _ => foldOver(x, tree) + } + } +} + +object SpecializeInlineTraits: + val name: String = "specializeInlineTraits" + val description: String = "inline the code of inline traits" + + private[transform] def newInnerClassName(name: Name): name.ThisName = name ++ str.INLINE_TRAIT_INNER_CLASS_SUFFIX diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 8163abbc1588..eccb256be6b8 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -89,8 +89,8 @@ class Splicing extends MacroTransform: case tree: Quote => val body1 = QuoteTransformer().transform(tree.body)(using quoteContext) cpy.Quote(tree)(body1, tree.tags) - case tree: DefDef if tree.symbol.is(Inline) => - // Quotes in inlined methods are only pickled after they are inlined. + case _: DefDef | _: TypeDef if tree.symbol.is(Inline) => + // Quotes in inlined methods and traits are only pickled after they are inlined. tree case _ => super.transform(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 0077cb969e3a..545315afc8f2 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -109,7 +109,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { if (sym.isTerm && !sym.is(Method, butNot = Accessor) && !ctx.owner.isAllOf(ParamForwarder)) // ParamForwaders as installed ParamForwarding.scala do use super calls to vals - report.error(em"super may be not be used on ${sym.underlyingSymbol}", sel.srcPos) + report.error(em"super may not be used on ${sym.underlyingSymbol}", sel.srcPos) else if (isDisallowed(sym)) report.error(em"super not allowed here: use this.${sel.name} instead", sel.srcPos) else if (sym.is(Deferred)) { @@ -156,6 +156,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val needAccessor = name.isTermName // Types don't need super accessors && !sym.isInlineMethod // Inline methods are not called at runtime so they don't need superaccessors. + && !clazz.isInlineTrait // Inline traits deal with super calls in their own way && (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/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index e725eaef939e..b4298d9a933c 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -72,7 +72,11 @@ class TreeChecker extends Phase with SymTransformer { sym.isRefinementClass assert(validSuperclass, i"$sym has no superclass set") - testDuplicate(sym, seenClasses, "class") + + // Multiple references to specialized traits will specialize multiple times, but they lead to the same + // interface and implementation classes every time, so we can allow duplicates and pick one arbitrarily. + if !(sym.isSpecializedTraitInterface || sym.isSpecializedTraitImplementationClass) then + testDuplicate(sym, seenClasses, "class") } val badDeferredAndPrivate = @@ -599,8 +603,14 @@ object TreeChecker { def isNonMagicalMember(x: Symbol) = !x.isValueClassConvertMethod && - !x.name.is(DocArtifactName) - + !x.name.is(DocArtifactName) && + !(x.owner.isInlineTrait) + // ^TODO: Not sure if this is strictly necessary but it seems to help in some cases because + // we can generate undefined private members in inline traits - we might need to think about + // this a bit more because I think it's only fine if you assume that you can't directly instantiate + // inline traits, whereas we might want to allow that (aside from specialisation) . + + val decls = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMember) val defined = impl.body.map(_.symbol) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index a8a855b6ae5b..12e93f7e1f3b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -635,8 +635,9 @@ object Semantic: reporter.report(error) Hot else - val error = AccessNonInit(target)(trace) - reporter.report(error) + if !receiver.classSymbol.isInlineTrait then // See tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala + val error = AccessNonInit(target)(trace) + reporter.report(error) Hot else report.warning("[Internal error] Unexpected resolution failure: ref.klass = " + ref.klass.show + ", field = " + field.show + Trace.show, Trace.position) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 58f38d10a2b3..cc30fbca1393 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -689,7 +689,7 @@ object Checking { if (sym.isConstructor && !sym.isPrimaryConstructor && sym.owner.is(Trait, butNot = JavaDefined)) val addendum = if ctx.settings.Ydebug.value then s" ${sym.owner.flagsString}" else "" fail(em"Traits cannot have secondary constructors$addendum") - checkApplicable(Inline, sym.isTerm && !sym.is(Module) && !sym.isMutableVarOrAccessor) + checkApplicable(Inline, sym.isTerm && !sym.is(Module) && !sym.isMutableVarOrAccessor || sym.is(Trait)) checkApplicable(Lazy, !sym.isOneOf(Method | Mutable)) if (sym.isType && !sym.isOneOf(Deferred | JavaDefined)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index cfc5de9b176f..71bf4012d201 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1022,7 +1022,7 @@ class Namer { typer: Typer => val sym = denot.symbol def register(child: Symbol, parentCls: ClassSymbol) = { - if (parentCls.is(Sealed)) + if (parentCls.is(Sealed) && !(child.isAnonymousClass && parentCls.isSpecializedTrait)) if ((child.isInaccessibleChildOf(parentCls) || child.isAnonymousClass) && !sym.hasAnonymousChild) addChild(parentCls, parentCls) else if (!parentCls.is(ChildrenQueried)) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 2940135f53ed..29ffd210d1e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -417,6 +417,8 @@ object RefChecks { * of class `clazz` are met. */ def checkOverride(member: Symbol, other: Symbol): Unit = + def isInlinedFromInlineTrait = other.owner.isAllOf(InlineTrait) && member.is(Synthetic) + def memberType(self: Type) = if (member.isClass) TypeAlias(member.typeRef.etaExpand) else self.memberInfo(member) @@ -507,12 +509,13 @@ object RefChecks { def overrideTargetNameError() = val otherTargetName = i"@targetName(${other.targetName})" - if member.hasTargetName(member.name) then - overrideError(i"misses a target name annotation $otherTargetName") - else if other.hasTargetName(other.name) then - overrideError(i"should not have a @targetName annotation since the overridden member hasn't one either") - else - overrideError(i"has a different target name annotation; it should be $otherTargetName") + if !isInlinedFromInlineTrait then + if member.hasTargetName(member.name) then + overrideError(i"misses a target name annotation $otherTargetName") + else if other.hasTargetName(other.name) then + overrideError(i"should not have a @targetName annotation since the overridden member hasn't one either") + else + overrideError(i"has a different target name annotation; it should be $otherTargetName") //Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG @@ -548,13 +551,13 @@ object RefChecks { // direct overrides were already checked on completion (see Checking.chckWellFormed) // the test here catches indirect overriddes between two inherited base types. overrideError("cannot be used here - class definitions cannot be overridden") - else if (other.isOpaqueAlias) + else if (other.isOpaqueAlias && !isInlinedFromInlineTrait) // direct overrides were already checked on completion (see Checking.chckWellFormed) // the test here catches indirect overriddes between two inherited base types. overrideError("cannot be used here - opaque type aliases cannot be overridden") else if (!other.is(Deferred) && member.isClass) overrideError("cannot be used here - classes can only override abstract types") - else if other.isEffectivelyFinal then // (1.2) + else if (other.isEffectivelyFinal && !isInlinedFromInlineTrait) then // (1.2) overrideError(i"cannot override final member ${other.showLocated}") else if (member.is(ExtensionMethod) && !other.is(ExtensionMethod)) // (1.3) overrideError("is an extension method, cannot override a normal method") @@ -597,7 +600,7 @@ object RefChecks { overrideError("needs `override` modifier") else if (other.is(AbsOverride) && other.isIncompleteIn(clazz) && !member.is(AbsOverride)) overrideError("needs `abstract override` modifiers") - else if isMarkedOverride(member) && other.isMutableVarOrAccessor then + else if isMarkedOverride(member) && other.isMutableVarOrAccessor && !isInlinedFromInlineTrait then overrideError("cannot override a mutable variable") else if isMarkedOverride(member) && !(member.owner.thisType.baseClasses.exists(_.isSubClass(other.owner))) @@ -649,6 +652,7 @@ object RefChecks { overrideError("cannot have a @targetName annotation since external names would be different") else if other.is(ParamAccessor) && !isInheritedAccessor(member, other) && !member.is(Tracked) // see remark on tracked members above + && !other.owner.isInlineTrait // Allow inline traits to override val params because we prune the params from the parent traits later so they need to live in the children. then // (1.12) report.errorOrMigrationWarning( em"cannot override val parameter ${other.showLocated}", diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index ea6d97ec4321..6ba16634a3fa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -681,6 +681,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val synthesizedMirror: SpecialHandler = (formal, span) => orElse(synthesizedProductMirror(formal, span), synthesizedSumMirror(formal, span)) + val synthesizedSpecialized: SpecialHandler = (formal, span) => formal match { + case AppliedType(tycon, arg :: Nil) if tycon =:= defn.SpecializedClass.typeRef => + withNoErrors(TypeApply(ref(defn.SpecializedModule_apply), TypeTree(arg) :: Nil)) + case _ => EmptyTreeNoError + } + private enum ManifestKind: case Full, Opt, Clss @@ -804,6 +810,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): defn.OptManifestClass -> synthesizedOptManifest, defn.SingletonClass -> synthesizedSingleton, defn.PreciseClass -> synthesizedPrecise, + defn.SpecializedClass -> synthesizedSpecialized ) def tryAll(formal: Type, span: Span)(using Context): TreeWithErrors = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4f355cbd87e2..76671a02090e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3524,6 +3524,29 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer addAccessorDefs(cls, typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))) + if !ctx.isAfterTyper && cls.isInlineTrait then + body1.map(_.symbol).filter(_.isInlineTrait).foreach(innerInlTrait => + report.error( + em"Implementation restriction: an inline trait cannot be defined inside of another inline trait", + innerInlTrait.srcPos + ) + ) + val membersToInline = body1.filter(member => Inlines.isInlineableFromInlineTrait(cls, member)) + membersToInline.foreach { + case tdef: TypeDef if tdef.symbol.isClass => + def rec(paramss: List[List[Symbol]]): Unit = paramss match { + case (param :: _) :: _ if param.isTerm => + report.error(em"Implementation restriction: inner classes inside inline traits cannot have term parameters", param.srcPos) + case _ :: paramss => + rec(paramss) + case _ => + } + rec(tdef.symbol.primaryConstructor.paramSymss) + case _ => + } + val wrappedMembersToInline = Block(membersToInline, unitLiteral).withSpan(cdef.span) + PrepareInlineable.registerInlineInfo(cls, wrappedMembersToInline) + checkNoDoubleDeclaration(cls) val impl1 = cpy.Template(impl)(constr1, parents1, Nil, self1, body1) .withType(dummy.termRef) @@ -4904,13 +4927,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree } else TypeComparer.testSubType(tree.tpe.widenExpr, pt) match - case CompareResult.Fail(_) => + case CompareResult.Fail(notes) => wtp match case wtp: MethodType => missingArgs(wtp) case _ => typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") //typr.println(TypeComparer.explained(tree.tpe <:< pt)) - adaptToSubType(wtp) + adaptToSubType(wtp, notes) case CompareResult.OKwithGADTUsed if pt.isValueType && !inContext(ctx.fresh.setGadtState(GadtState(GadtConstraint.empty))) { @@ -5011,7 +5034,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: Closure => cpy.Closure(tree)(tpt = TypeTree(samParent)).withType(samParent) } - def adaptToSubType(wtp: Type): Tree = + def adaptToSubType(wtp: Type, cmpNotes: List[Message.Note] = Nil): Tree = // try converting a constant to the target type tree.tpe.widenTermRefExpr.normalized match case ConstantType(x) => @@ -5080,7 +5103,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else val tree1 = healAdapt(tree, pt) if tree1 ne tree then readapt(tree1) - else err.typeMismatch(tree, pt, failure.notes) + else err.typeMismatch(tree, pt, cmpNotes ++ failure.notes) pt match case _: SelectionProto => diff --git a/docs/_docs/internals/inline-traits.md b/docs/_docs/internals/inline-traits.md new file mode 100644 index 000000000000..80a63ff5d162 --- /dev/null +++ b/docs/_docs/internals/inline-traits.md @@ -0,0 +1,436 @@ +# Inline Traits + +## Motivation +Inline traits is a new attempt to solve the specialization problem for the JVM in a more convenient way than the `@specialized` annotation +from Scala 2 (the main problem of this being code bloat as we generated all possible specializations at declaration time of the specialized +class). Inline traits work alongside `Specialized` traits, the latter being detailed in an accompanying document. + +The problem is as follows: due to the JVM's (lack of) support for generics, generic type parameters are erased by the compiler: +```scala +class A[T](val x: T) + +class C: + val w1 = A[Int](1) + val w2 = A[Int](2) + val w3 = A[Int](w1.x + w2.x) +``` +Is converted to: +```scala +class A: + val x: Object +class C() extends Object() { + def w1(): A = new A(Int.box(1)) + def w2(): A = new A(Int.box(2)) + def w3(): A = new A(Int.box(Int.unbox(this.w1().x()).+(Int.unbox(this.w2().x())))) +} +``` +Thus type `T` is converted to `Object`, and so every use of `A[Int]()` must first build an `Object` containing the `Int` we want to pass (*boxing*), +in order to be able to call `A(x: Object)`. There is no `A(x: int)`. When referencing `x` on an `A` we get an `Object` back, which we have to *unbox* +(extract from the wrapping `Object`). + +This object creation and deletion is very slow. We desire a way to avoid this by generating specialized instances of classes which +use primitive types instead of `Object`. These can be used in situations where the (un)boxing overhead is likely to be high. + +## Solution +An inline trait is defined just like a normal trait, but with an `inline` modifier. + +Inline traits may be freely extended by objects, classes or other inline traits. An inline trait may also be extended by an ordinary trait, but only if the inline trait does not take parameters [***]. + +The following is an example of the use of an `inline trait`. + +```scala +inline trait A[T](val x: T): + def foo: T = x + +class B extends A[Int](1) +``` +Let the term *inline trait* refer to traits such as `A` above, and let *inline receiver* refer to class-likes +we inline into, such as `B` above. + +When an inline trait is inherited by an object, class or another inline trait, all its contents are inlined, and adapted +to the context of the inline receiver. In particular this means that: +- references to type parameters of the inline trait are specialized to the type arguments provided during extension +- `this` calls are updated to refer to the inline receiver. + +Inline traits are themselves translated to pure interfaces. However their bodies are of course retained in Tasty files; this +enables us to inline them into inline receivers that exist in different compilation units (for example when an inline recevier +in user code extends an inline trait from a library). + +The example above generates: +```scala +// Inline trait converted to pure interface +inline trait A[T](x: T): + val x: T + def foo: T + +// Extending class now contains inlined body +// with references to T specialized to Int. +// this.x refers to B.x +class B extends A[Int](1): + override val x: Int = 1 + override def foo: Int = this.x +``` + +With multiple inline traits: + +```scala +inline trait A[T](val x: T): + def foo: T = x + +inline trait B extends A[Int] +class C extends B, A[Int](1) +``` + +```scala +inline trait A[T](x: T): + val x: T + def foo: T + +inline trait B extends A[Int]: + override def x: Int + override def foo: Int + +class C extends B, A[Int](1): + override def x = 1 + override def foo = x +``` +[!] This may be surprising compared to inline methods, as calls to inline methods from other inline methods are only inlined +when the outer inline method is inlined. While it is not immediately obvious why permitting inline traits to be inlined into other inline traits is useful (we could simply +inline everything into the first receiver which is not inline in the hierarchy), it becomes advantageous when we bring in the `Specialized` annotation; see the accompanying document. +Of course, patterns creating cycles of inlining are banned: + +```scala +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +``` + +Furthermore: +- References to members of inline traits accessed on inline receivers point to the inlined version, to ensure we avoid unnecessary boxing: [1] +```scala +inline trait A[T](val x: T): + def foo#1: T + +class B extends A[Int](1) + def foo#2: T = x + +def fun(x: B) = + x.foo // points to foo#2 +``` +- Inline traits may define private members, and these are handled specially: [2] + - Private fields in the inline trait are inlined as private fields with a mangled name in the inline receiver. This ensures they do not collide with privates inherited from other inline traits. + - The private fields are then no longer accessible in the inline trait, as it is transformed into a pure interface, so we delete them. + +```scala +inline trait A(b: Boolean): + private val x: Int = 1 + def foo(): Int = if b then x + 1 else 0 + +class B extends A(true) +``` +Is converted to: + +```scala +inline trait A(b: Boolean): + def foo(): Int + +class B extends A(true): + private val A$$b: Boolean = true + private val A$$x: Int = 1 + override def foo(): Int = if this.A$$b then this.A$$x.+(1) else 0 +``` +- An inline receiver may mix in multiple inline traits with colliding member names. This follows the same rules as normal traits. In particular, this must usually be disambiguated with an override. +```scala +inline trait A: + def foo = "Hello World" + +inline trait B: + def foo = "Bonjour" + +class C extends A, B // error: C inherits conflicting members A.foo and B.foo +``` +A typical way to disambiguate would be using `super`. For example: +```scala +class C extends A, B: + override def foo = super[A].foo +``` +This syntax is supported for inline traits. Note however that as inline traits are converted to pure interfaces it is not possible to make a direct call to the +method on A or B. Furthermore if we allowed this, specialization would be lost. Therefore, overridden methods are inlined into the inline receiver with a mangled name, +e.g. `A$$foo$`, `B$$foo` and the `override def foo` in `C` will delegate to one of these methods. Super calls to non-overridden methods are also supported. +These are transformed to point directly to the corresponding inlined methods with no need for name mangling. + +- **Interaction with other types of inline**: + + - Inline traits may define inline members (e.g. `inline def`, `inline val`). References to these are inlined as the body of the trait is inlined into the inline receiver, but the members themselves are not inlined and are deleted from the parent trait. E.g.: + + ```scala + inline trait A: + inline val x = 1 + + class B extends A: + def f = x + ``` + + becomes: + + ```scala + inline trait A + class B extends A: + def f = 1 + ``` + - As is usual, `inline val`s must have constant value types. In particular this means that they may not take the value of a parameter to the inline trait: + + ```scala + inline trait A[T](x: T): + inline val y = 1 + inline val a = y // ok + inline val z = x // Not ok + ``` + +- There is the potential for name clashes between members / parameters of inline traits and parameters / members of inline receivers. These are handled in the following way: + +| Inline Trait Member Type | Inline Receiver Member Type | Behaviour | Justification | Same as `trait` | +|---------------------------------------|----------------------------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| `val` / `var` incl. `val`/`var` param | `val` / `var` incl. `val` / `var` param | Needs `override`| Ensures behaviour matches that of normal traits. However note that normal traits will warn if we try to override a val parameter. This warning is turned off for inline traits, otherwise every inline trait with a val parameter would warn after inlining. | ✅ | +| `val` / `var` incl. `val`/`var` param | Primary constructor (local) param | Not allowed | We cannot rename the constructor param because users may specify it by name when constructing the class, and we can't have a conflict with the generated parameter accessor, yet the parameter accessor and parameter must have the same name, so we end up with a conflict between the inlined val/var param and the constructor param. Therefore we have to ban this. | ❌ | +| `val` / `var` incl. `val`/`var` param | Method | Not allowed | As normal traits, without `override` will be told "needs override"; with `override` will be told "not a stable immutable value" | ✅ | +| Primary ctor (local) param | `val` / `var` incl. `val` / `var` param | Allowed | We need to inline the generated parameter accessors into the receiver, but these are renamed (prefixed with the inline trait name) when doing so, therefore fine. This does not affect the name in the original trait. | ✅ | +| Primary ctor (local) param | Primary constructor (local) param | Allowed | We need to inline the generated parameter accessors into the receiver, but these are renamed (prefixed with the inline trait name) when doing so, therefore fine. This does not affect the name in the original trait. | ✅ | +| Primary ctor (local) param | Method | Allowed | We need to inline the generated parameter accessors into the receiver, but these are renamed (prefixed with the inline trait name) when doing so, therefore fine. This does not affect the name in the original trait. | ✅ | +| Method | `val` / `var` incl. `val` / `var` param | Requires `override` | | ✅ | +| Method | Primary constructor (local) param | Allowed unless sig match | We have the same issue as with `val`/`var` params if the signature matches, so we need to ban it. Otherwise we allow it as in `trait`. | ❌ | +| Method | Method | Requires `override` | | ✅ | +| Type | Type | Allowed | Usual rules apply | ✅ | + +- Inline receivers may not access the parameters of their parents (these are private): +```scala +inline trait A(x: Int) + +class C extends A(10): + val y = x // error: Not Found Error +``` + +- Inlined members of inline traits are typed with the type of the right hand side resulting from inlining. This is particularly important for typeclass instances: +```scala +inline trait A[T: Numeric]: + private val v: Numeric[T] = summon[Numeric[T]] + +class B extends A[Int] +``` +Is converted to: +```scala +inline trait A[T: Numeric]: + private val v: Numeric[T] = summon[Numeric[T]] + +class B extends A[Int]: + private given val A$$evidence$1: Numeric.IntIsIntegral.type = Numeric.IntIsIntegral + private val A$$v: Numeric.IntIsIntegral = this.A$$evidence$1 +``` +This means that references to `v.fromInt()`, `v.add()` etc are optimised and avoid boxing. However, this type acquisition is only applied to non-var members, as it could +lead to unsoundness if applied to `var`s: + +```scala +inline trait Counter extends Iterator: + private var current: Int = 0 + def next(): Int = current += 1 +``` +Narrowing `current` to the type of the initializer here would give it type `0`. This makes the increment operation in `next()` illegal. + +- Inline methods defined inside an inline trait are inlined directly when the body is inlined. This means that they do not exist in the inline receivers. They are then deleted from the inline trait. + +[***] Why? +Consider: +```scala +inline trait A[T](x: T): + val y = x +trait B extends A[Int] +class C extends A[Int](10), B +``` +After inlining, `B.y` is defined in terms of `B.A$$x` (the inlined copy of the parameter accessor of `x`), but this is undefined as we don't have the parameter value in `B`. +In `C` this is not a problem as we have the parameter value `10`. Even if we try to provide the value by leaving `A$$x` abstract and overriding in `C` +this will not work as `B.A$$x` will still be undefined. + +Therefore there is no case in which this could be useful, and it is likely to cause confusion, so we ban it. +However, there is a reasonable case for `trait extends inline trait` in general, to control the inlining and reduce code duplication so we allow +this pattern if the inline trait has no parameters. + +Note that this restriction does not block `inline trait extends trait` or `inline trait extends class` which are allowed with one (other) restriction: + - Inline traits may not contain `super` references to classes or non-inline traits. This is because `super` references in scala may only reference + direct parents, and, after inlining, those references that were to direct parents in the original inline traits would now have to point to ancestor // TODO: I think this ought to be fine actually because we can just call into the inline trait + classes which are further than 1 hop away. + +## Benefits of inline traits +We can now do the following with no boxing and unboxing: +```scala +inline trait A[T](val x: T) +class IntA(x: Int) extends A[Int](x) + +class C: + val w1 = IntA(1) + val w2 = IntA(2) + val w3 = IntA(w1.x + w2.x) +``` + +Inline traits avoid all of the pain points of Scala 2 specialization. They can do more than primitive specialization since they also specialize on value parameters and reference types. This helps avoid megamorphic dispatch. Inline traits also profit from all the optimizations available for inline methods, including inline matches, summonFrom, embedded splices. Indeed the analogy of inline traits and inline methods is strong: inline calls correspond to supercalls in extending classes and objects, inline parameters are inline parameters of the trait, inline traits can have nested inline matches, etc. + +Inline trait expansions are only generated on demand when a class or object extends an inline trait. This avoids the up-front cost and code explosion due to creating specialized copies which might never be needed. + +## Shortcoming of inline traits +Compared to full specialization, inline traits have one shortcoming, namely that interfaces are not specialized. For example: + +```scala +inline trait Foo[T](x: T): + def foo = x + +class Bar extends Foo[Int](42) + +def f(b: Foo[Int]) = 37 + b.foo + +@main def main = + val x = Bar() + f(x) +``` + +In this code the call to `b.foo` will refer to the version of `foo` typed `foo: T` which becomes `foo: Object` during erasure, because we accessed `foo` on +an object of declared type `Foo` (even though `b`'s actual runtime type is `Bar`). This will in turn call a bridge method which means the `foo: Int` method will be called, but unnecessary boxing and unboxing will be added: + +```scala +inline trait Foo + def foo#1(): Object + +class Bar extends Foo[Int](42): + override def foo#2(): Int = 42 + override def foo#3(): Object = Int.box(this.foo()) + +def f(b: Foo): Int = 37 + Int.unbox(b.foo#1()) // virtual call to foo#1 resolves to bridge method foo#3, which in turn calls actual method foo#2, with boxing. +``` + +This problem is addressed via `Specialized` traits; see the accompanying document on those. + +## Interaction with other language features + +| Language feature | Is currently supported inside inline traits? | +|-------------------------------------|----------------------------------------------| +| Methods | ✅ | +| `val` / `var` Properties | ✅ | +| Non-local private members [3] | ❌ | +| `type`s | ✅ | +| Inner classes/traits | ❌ [7] | +| Self types | ✅ [6] | +| Inheritance (of inline traits) | Only allowed by classes and inline traits | +| Instantiation of inline traits [4] | ✅ | +| Opaque types | ✅ [5] | + +[3] That is, members which are labelled private and accessed from within the class on other instances of the class. +Local private members (members with the same access patterns as the former `private[this]`) are allowed. + +[4] As long as this doesn't create a cycle e.g.: +```scala +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +class T extends D // error: Inlining of inline traits looped. Tried to inline trait D into its own body. +``` + +[5] Supported with same behaviour as in normal traits. In particular, the following is completely fine, and will be inlined into B. +```scala +inline trait A[T](val x: T): + opaque type Special = T + + def getSpecial: Special = x + def eatSpecial(y: Special) = "Mmm, that was tasty!" + +class B extends A[Int](100) +``` + +In contrast, it is not possible to use inlining to "cheat" opaque types, even though it is tempting to try to argue that +the opaque type will be inlined into B and therefore its alias should be visible. This is not allowed because type checking +is performed before inline traits, and this follows the logic that inline traits are an optimisation on top of normal traits, +rather than a semantic change to them. +```scala +inline trait A[T](val x: T): + opaque type Special = T + +class B extends A[Int](100): + def foo: Special = 10 // error: 10 does not conform to Special +``` + +[6] +Self types are supported but are not inlined. We argue this is desirable as it ensures that the behaviour of inline traits +with self types mirrors that of ordinary traits with self types. Inlining of self types would effectively remove any restictions that +these self types seek to impose because the subclass would automatically have a matching self type to that of the parent class. This +prevents an error from being thrown, irrespective of whether the subclass implements the desired traits. +Therefore, when using self types on inline traits, the behaviour observed is the following (as in ordinary traits) e.g.: +```scala +trait A[T]: + this: T1 => + +trait D extends A[Int] // error: self type of D does not conform to that of A +``` + +[7] +While inline traits may not directly define inner classes, they may contain methods which define classes within their bodies. + +## Processing of inline traits in the compiler +Inline traits in user code are inlined in the phase `specializeInlineTraits`. The phase `replaceInlinedTraitSymbols` +is responsible for updating references to members of inline receivers to point to the inlined members, instead of the +generic members in the parent inline trait (see [1] above). Finally the phase `pruneInlineTraits` is responsible for +converting inline traits into pure interfaces by removing their right hand sides. It also handles the mangling in [2]. + +Specialized traits rely on the semantics of inline traits, as they desugar to inline traits. However, the phase +`desugarSpecializedTraits` inlines these inline traits itself (sharing code with the `specializeInlineTraits` phase). +This is necessary because otherwise there would be a circular dependency between the two phases (see the Specialized traits +document for more information). This means that we need to run `specializeInlineTraits` *first* (because we don't want to inline +twice for inline traits resulting from specialization). + +## Internal Note regarding versions of inline traits +This behaviour is the same as that in Timothée's thesis except for the following points: + - We now allow inline traits to be inlined directly into other inline traits as well as objects and classes. + - We now do replacement of member accesses to point to the inlined versions throughout the whole code, not just in the bodies of inner classes + - He allows inline traits to contain inner classes in principle, however in practice they don't work which is why we ban them. + - We specialize types of member accesses on e.g. Numeric + - He in practice allows traits to extend inline traits although it doesn't work that well and there was some suggestion it should have been banned; we tighten/specify the rules on this: + - Trait extends inline trait is only allowed if the inline trait is parameterless + - Inline trait extends trait is always allowed + - We modify some of the rules around overrides and conflicting members in order to make the behaviour more consistent with ordinary traits. + In particular we require `override` in a number of locations where previously conflicts were resolved on the basis of "last extending trait wins". + - We also fix a number of bugs in the implementation, some of which have a minor effect on the processing and interaction with the rest of the compiler phases, e.g. we apply pruneInlineTraits slightly earlier than in the original implementation to avoid spurious warnings with -Wsafe-init, and we fix flags, and add support for nested inlines. + - We also implement some extra inlining such as opaque types, super references, and self types. + - We enforce a number of rules that were previously implicit, with proper errors. + - We do the RHS type narrowing for vals (not vars) described above as an optimisation + - We change the handling of private members in pruning of inline traits (we now delete them completely) + - We change the phase ordering since we conclude that inline trait inlining must happen before pickling to get the benefit of specialization + across compilation units. Otherwise with the following under separate compilation we will induce boxing: + +```scala +// File A.scala +inline trait IT[T]: + def foo(x: T): T = x + +class A extends IT[Int] + +// File B.scala +def main = + val a = new A() + val x: Int = a.foo(10) // leads to Int.unbox(a.foo(Int.box(10))) +``` + + This happens because when B.scala is compiled separately against the interface of A (derived from the pickled A.tasty) it will appear that A only supports the generic T interface (erasing to Object and so boxed), whereas it actually also has a specialized Int interface from inline trait inlining. If instead we inline before pickling we solve this problem as the generated interface is present in the pickle. There is precedent for some inlining before pickling in e.g. transparent inlines, and if one uses inline traits one expects code duplication (that's why it is opt-in) and therefore we argue this is not a problem in terms of the increased tasty file size that it leads to. diff --git a/docs/_docs/internals/specialized-traits.md b/docs/_docs/internals/specialized-traits.md new file mode 100644 index 000000000000..76f155abaf95 --- /dev/null +++ b/docs/_docs/internals/specialized-traits.md @@ -0,0 +1,795 @@ +# Specialized Traits +Specialized traits accompany inline traits as a new attempt to solve to the specialization problem in Scala 3, replacing the `@specialized` annotation from Scala 2. + +As mentioned in the accompanying document on inline traits, inline traits have one shortcoming, namely that interfaces are not specialized. For example: + +```scala +inline trait Foo[T](x: T): + def foo: T = x + +class Bar extends Foo[Int](42) + +def f(b: Foo[Int]) = 37 + b.foo + +@main def main = + val x = Bar() + f(x) +``` + +In this code the call to `b.foo` will refer to the version of `foo` typed `foo: T` which becomes `foo: Object` during erasure, because we accessed `foo` on +an object of declared type `Foo` (even though `b`'s actual runtime type is `Bar`). This will in turn call a bridge method +which means the `foo: Int` method will be called, but unnecessary boxing and unboxing will be added: + +```scala +inline trait Foo + def foo#1(): Object + +class Bar extends Foo[Int](42): + override def foo#2(): Int = 42 + override def foo#3(): Object = Int.box(this.foo()) + +def f(b: Foo): Int = 37 + Int.unbox(b.foo#1()) // virtual call to foo#1 resolves to bridge method foo#3, which in turn calls actual method foo#2, adding boxing. +``` + +We introduce the typeclass `Specialized` that can be used as a context bound on type parameters of inline traits and inline methods. + +The `Specialized` annotation indicates that we want to create specialized versions of the inline trait where the type parameter is instantiated to the type argument, and replace (in erasure) uses of the inline trait with the corresponding specialized version so that member accesses pass through the specialized interface. + +A `Specialized` annotation on an inline method allows it to call other specialized methods or instantiate specialized traits using the specialized type variable (any code may instantiate specialized traits with concrete types). This allows us to transport information about possible specialization types through multiple layers of generic code (inline methods). + +## Example + +To give a flavour of what specialization does, consider a `Vec` trait for vectors over a numeric type: +```scala +import scala.math.Numeric + +inline trait Vec[T: {Specialized, Numeric}](elems: Array[T]): + private val num = summon[Numeric[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: Vec[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +def printVector(v: Vec[Int]) = println(v) + +object Vec: + inline def apply[T: Specialized](elems: Array[T]) = new Vec[T](elems) {} +end Vec + +val v = Vec[Int](Array(1, 2, 3, 4, 5)) +``` + +Specialization will generate: +```scala +inline trait Vec$sp$Int extends Vec[Int]: + def length: Int + def apply(i: Int): Int + def scalarProduct(other: Vec$sp$Int): Int + +class Vec$impl$Int(elems: Array[Int])(using Numeric[Int]) extends Vec[Int](elems), Vec$sp$Int: + private val Vec$$num = summon[Numeric[Int]] + + def length = elems.length + + def apply(i: Int): Int = elems(i) + + def scalarProduct(other: Vec$sp$Int): Int = + require(this.length == other.length) + var result = Vec$$num.fromInt(0) + for i <- 0 until length do + result = Vec$$num.plus(result, Vec$$num.times(this(i), other(i))) + result + +def printVector(v: Vec$sp$Int) = println(v) + +object Vec: + inline def apply[T: Specialized](elems: Array[T]) = new Vec[T](elems) {} +end Vec + +val v = Vec$impl$Int(Array(1, 2, 3, 4, 5)) +``` + +This provides a number of potential efficiency gains: + - Avoid boxing in the API for values like the result of `scalarProduct` (as we will access the method on the specialized version). + - Specialize on the concrete `Numeric` class instance for `T`, so that calls to `num`'s methods have static targets and can be inlined, and avoid boxing for internal values like `result` (if we use define and use a specialized version of Numeric). + - Use an array `elems` specialized to the actual element instead of a fully generic array that has to be accessed via reflection. (The rest of the compiler already generates `int[]` when the source references `Array[Int]` so this comes for free through inline trait specialization). + +## Creating Specialized Trait Instances +Creation of an object with specialized behaviour can occur in one of two ways: + - Instantiating a `class` or using an `object` [1, 2] which extends the specialized trait, specializing its type parameters. E.g.: +```scala +inline trait Foo[T: Specialized](x: T): + def foo: T = x + +class Bar extends Foo[Int](10): // Type parameter does of course not need to be specified explicitly here (it can be inferred from the value type of 10) + def myMethod = "Hello I am a method" + +// Both Baz and myBar will have specialized versions of foo. +object Baz extends Foo(12) +val myBar = Bar() +``` + - Instantiation of an anonymous class instance directly from the Specialized trait: `new A[Ts](ps1)...(psN) {}` where `A` is a specialized trait and the type parameters `Ts` and term parameters `ps1, ,,, psN` can also be absent. This has a special meaning, as it desugars to instantiating a _specialized instance class_ (`$impl$` class, see Expansion of Specialized Traits). This comes with the twin advantages that: + - there is no need for the boilerplate of manually defining an object/class to extend the specialized trait + - this specialized instance class is reused every time such an instance is created (there is no proliferation of anonymous classes). + + However, to facilitate this reuse, we must impose the following restrictions. Anonymous class instances acting as instances of Specialized traits: + - can extend only a single specialized trait [0], + - cannot mix in further classes or traits, and + - cannot contain member definitions. + + Should these restrictions be undesirable, the user can always create their own named `object` or `class` extending from a specialized trait (i.e. the first case for creation of a specialized object just above), which does not induce these restrictions. + +[0] Note that because the specialized traits take evidence parameters for the `Specialized` typeclass, `new Foo[Int] {}` where `Foo` extends some other specialized trait `Bar` desugars in the compiler to `new Bar[Int] with Foo[Int] {}`, which means we can't distinguish these two cases. Therefore we allow +e.g. `new Bar[Int] with Foo[Int] {}` although there is no reason to use this in source code because it's exactly the same as writing `new Foo[Int] {}`. + +[1] Note that 'class extends specialized trait' and 'object extends specialized trait' are allowed. However, 'trait extends specialized trait' is not. This is due to the restriction on extending inline traits with parameters by ordinary traits, as discussed in `inline-traits.md`. 'inline trait extends specialized trait' is allowed as it is not subject to this restriction. + +[2] As explained in detail in _Specialized Traits Expansion Specification_, specialized instance traits and implementation classes have a special erasure. Classes/objects extending a specialized trait also have special erasure behaviour which adds both the specialized parents and ordinary parents: + +```scala +class Bar extends Foo[Int](10) with Baz[Int](10) +class Bar extends Object, Foo(10), Bar(10), Foo$sp$Int, Bar$sp$Int +``` +This is necessary so that Bar can be treated as a `Bar$sp$Int` or `Foo$sp$Int` (in general we may not have `Bar$sp$Int` extends `Foo$sp$Int` so we add both) and to pass arguments to `Foo` and `Bar`. + +## Specialized Traits Expansion Specification +The following is a technical specification for the expansion of specialized traits. + +### Expansion of Specialized Traits +**Definition**: A _specialized trait_ is an inline trait that has at least one `Specialized` context bound. A specialized context bound (or its expansion to a context parameter) is only allowed for +type parameters of inline methods and inline traits. Regular methods or traits or classes +cannot take `Specialized[T]` parameters. + +**Definition**: A _simple class type_ is a reference to a static class that does not have type parameters. References to traits and references containing non-static prefixes or refinements are excluded. + +**Definition**: A _top class_ is one of `Any`, `AnyVal`, or `Object` (also `AnyRef` which is a synonym of `Object`). + +**Definition**: The _specializing supertype_ `SpecType(Tp)` of a type `Tp` is defined as follows: + - `SpecType(Tp) =` the smallest simple class type `C` such that: + - `C` is a supertype of `Tp` + - The superclass of `C` is a top class, or `C` is itself a top class. +- `SpecType(Nothing) = Nothing` + +> By smallest simple class type we mean in the sense of smallest set of values that the type contains, i.e. the closest to the leaves of the class hierarchy. This simply means we may have a choice between `Foo` and `Any`, with `Foo <:< Any` and we will select `Foo`. + +**Definition** A _specialization_ `SpecTrait[T1, T2, T3, ...]` is a reference to a specialized trait with concrete type arguments. + +The presence of a specialization `SpecTrait[T1, T2, T3, ...]` in the program implies the creation of a _specialized interface trait_. This trait will be named `SpecTrait$sp$C1$C2$C3`, where `$sp$` is a fixed specialization marker and `Cn` is an encoding of the fully qualified name of `SpecType(Tn)`. This means that the name of the specialized interface trait reflects in its name all specializing supertypes of the type arguments passed to specialized type parameters in sequence. Arguments passed to non-specialized type parameters are ignored. + +The presence of an anonymous class instance creation like `new Vec[T](elems) {}` in the program implies the creation of both a specialized interface trait and a _specialized instance class_, named `Vec$impl$TN`. The class name derives from the trait name by replacing `$sp$` with `$impl$`. + +Should the `SpecType` of all of the concrete type arguments to a specialization be a top class or `Nothing`, a specialized interface trait is not produced. However, should such a specialization require an instance class, an instance class will be created and will be named `SpecTrait$impl`. This is advantageous as it means that anonymous class instances extending specialized traits are _always_ replaced by an instance class. + +Replacement of specializations with their corresponding specialized interface traits and anonymous specialized instances with their corresponding specialized instance classes happens via erasure. The _erasure_ of `SpecTrait[T1, T2, T3, ...]` where `SpecType(Tn) = Cn` is: + + - If, for all n, `Cn` is one of the top classes `Any`/`AnyRef`/`AnyVal`/`Object`, or `Cn` is `Nothing`, the usual erased trait `Vec`. + - If for at least one n, `Cn` is some other class, the corresponding specialized interface trait `SpecTrait$sp$C1$C2$C3`. + +We also erase `new SpecTrait[T1, T2, T3, ....]` to: + - `SpecTrait$impl()` if all type arguments are top classes or nothing. + - `SpecTrait$impl$C1$C2$C3()` otherwise. + +The specialized interface traits and implementation classes are created on demand the first time the corresponding specialization or anonymous class is mentioned in the program. + +### Specialized interface traits definition + +A specialized interface trait takes the form: + +```scala +inline trait Vec[T: Specialized] extends Seq[T] +inline trait Vec$sp$Int extends Vec[Int], Seq$sp$Int +``` + +In general a specialized interface trait that specializes an inline trait `A[T]` with type argument `S`: + - drops all `Specialized` trait parameters of `A` + - adds `A[S]` as first parent trait + - _also_ adds all parents of `A` *in their specialized forms*, + - contains declarations from the body of `A` specialized to the type(s) in question (via inline trait inlining) + - is also an `inline trait`. This is for consistency; see [1]. + - does not take value parameters (including evidence parameters) + +We also allow 2 variants of "partial specialization": +```scala +inline trait Vec[T: Specialized, S] // Specialized traits defined with some non-specialized parameters +val x = new Vec[Int, String]() {} + +inline trait Foo[T: Specialized, S: Specialized] +inline trait Bar[T: Specialized] extends Foo[T, Int] // Specialized to a specialized type variable. +class Baz extends Bar[Boolean] +``` +In the first of these cases, the generated specialized interface traits will get type parameters: +```scala +inline trait Vec$sp$scala$Int[S]() extends Vec[Int, S] +val x = new Vec$impl$scala$Int() +``` + +In the other case, specialization waits until all the types are known, except for `Bar` which gets an additional `Foo$sp$Any$Int` parent. +```scala +inline trait Bar[T: Specialized] extends Foo[T, Int], Foo$sp$Any$Int +inline trait Foo$sp$Any$Int extends Foo[Any, Int] +inline trait Foo$sp$Boolean$Int extends Foo[Boolean, Int] +inline trait Bar$sp$Boolean extends Bar[String], Foo$sp$Boolean$Int +class Baz extends Foo, Bar, Foo$sp$Boolean$Int, Bar$sp$Boolean +``` + +### Specialized instance class traits definition + +A specialized instance class for an inline trait `A` at specialized argument `S` + - repeats the value parameters of inline trait `A` + - extends `A[S]` with these parameters + - extends the corresponding `$sp$` trait + +For example, here is the specialized instance class for `Vec` at `Int`: + +```scala +class Vec$impl$Int(elems: Array[T]) extends Vec$sp$Int, Vec[Int](elems) +``` +The bodies of the interface traits and instance classes are completed by inlining from the inline traits that they extend. + +## Relationship to other language features + +### Variance and Specialized Traits +Specialized traits may define variance parameters e.g.: +```scala +inline trait MyFunction1[-T1: Specialized, +R: Specialized] +``` +However, variance works in Scala because type parameters are erased, and so we can freely cast e.g. List[Lion] to List[Animal] at runtime. Because specialized traits have a special erasure (and necessarily, because this is how we get the specialization), a couple of variance patterns that are possible with standard traits are not possible with specialized traits. This concerns the top classes and `Nothing`. + +In the contravariance case: +```scala +inline trait RecyclingBin[-T: Specialized]: + def recycle(x: T) = println(s"Recycling ${x}") + +def recycleAnInteger(rbin: RecyclingBin[Int]) = + rbin.recycle(100) + +recycleAnInteger(new RecyclingBin[Any]() {}) // RecyclingBin[Any] can be interpreted as RecyclingBin[Int] due to contravariance +recycleAnInteger(new RecyclingBin[AnyVal]() {}) // RecyclingBin[AnyVal] can be interpreted as RecyclingBin[Int] due to contravariance + +// Yet, this erases to: +def recycleAnInteger(rbin: RecyclingBin$sp$Int) = + rbin.recycle(100) + +recycleAnInteger(RecyclingBin$impl().asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin$impl cannot be cast to RecyclingBin$sp$Int; Any and Int live in different erasure "buckets" so their erased types are unrelated +recycleAnInteger(RecyclingBin$impl{}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin$impl cannot be cast to RecyclingBin$sp$Int +``` +Therefore we have the following issues: +- `RecyclingBin[Any]`, `RecyclingBin[AnyVal]` may not be passed to `RecyclingBin[Int]` whereas normally they would be able to be passed +- `RecyclingBin[Any]`, `RecyclingBin[Object / AnyRef]` may not be passed to `RecyclingBin[Paper]` whereas normally they would. + +These uses of contravariance can never succeed. Rather than allowing them to always fail at runtime, we reject them at compilation time. We impose an additional restriction on contravariance with specialized parameters: +- If (when `F1 >:> F2`) `A[F1]` is to be interpreted as `A[F2]` under `A[-T: Specialized]`, we require that `SpecType(F1) = SpecType(F2)`. Given `F1 >:> F2` and looking at the definition of `SpecType` this means concretely: + - `RecyclingBin[Any]`, `RecyclingBin[AnyVal]` may not be passed to `RecyclingBin[Int]` whereas normally they would + - `RecyclingBin[Any]`, `RecyclingBin[Object / AnyRef]` may not be passed to `RecyclingBin[Paper]` whereas normally they would. + - `RecyclingBin[Any]` may be passed to `RecyclingBin[Object]`, `RecyclingBin[AnyRef]`, `RecyclingBin[AnyVal]` as these all erase to `RecyclingBin`. + +Covariance has a similar problem: + - In general covariance works fine because it corresponds to interpreting `A[F1]` as `A[F2]` where `F1 <:< F2`. Either`A[F1]` and `A[F2]` both erase to the same type (`A$sp$SpecType(F2)`, or `A` if `F1` and `F2` are both top classes), or `A[F1]` erases to `A$sp$F1` and `A[F2]` erases to `A` (`F2` is a top class while `F1` is not). But `A$sp$F1` is a subtype of `A` by definition so the upcast will succeed (and upcasts are generally cheap compared to downcasts on the JVM so this is acceptable from a performance perspective). + - The only exception is with `Nothing`, because we want to interpret `A[Nothing]` as e.g. `A[Int]`, but we erase `A[Nothing]` to `A`. `A >:> A$sp$Int` so this doesn't work and so we also have to reject covariance using `Nothing`. To implement this correctly would require a specialization `A$sp$Nothing` which extends every single specialization in the program, and this was deemed infeasible. The lack of covariance with `Nothing` makes the code less ergonomic in some cases. For example `case object Nil extends List[Nothing]` has to become `inline trait Nil[T: Specialized] extends List[T]` and `inline def apply[T: Specialized] = new Nil[T] () {}`, but we don't lose too much expressivity. + +### Other implementation restrictions on specialized traits +These could be lifted with additional work. + +| Behaviour | Limitation | Chance of fixing / limited by | +|--------------------------|-----------------------------------------------|---| +| Use of `?` bounds | May not be used for Specialized parameters; however may be used for non-Specialized parameters in specialized traits. | It was thought that this would be challenging because it would require bridge methods that "despecialize" to ensure that `A$sp$Int` implements `A`'s interface. In the end these bridge methods have been implements, so it should be very possible to lift this restriction as future work.| +| Defining specialized traits inside traits/classes/objects | May define specialized traits inside `object`s. May not define them inside `class`es or `trait`s. | Limited by the way we flatten the owners of generated `$impl$` classes and `$sp$` traits. We really want to build the `Foo$impl$` class directly next to `Foo`. For a single CU this would be possible if we walked the entire tree and found where these belong; for multiple CUs it is more complicated as the tree may not exist in the current CU. The case of path dependent specialized traits was deemed niche enough to not be high priority, and for multiple CUs seems very tricky. | + +// GOT TO HERE + + + + + +## A Larger Case Study + +As an example of a hierarchy of specialized traits, consider the following small group of specialized collection traits: + +```scala +inline trait Iterator[T: Specialized]: + def hasNext: Boolean + def next(): T + +inline trait ArrayIterator[T: Specialized](elems: Array[T]) extends Iterator[T]: + private var current = 0 + def hasNext: Boolean = current < elems.length + def next(): T = try elems(current) finally current += 1 + +inline trait Iterable[T: Specialized]: + def iterator: Iterator[T] + def forall(f: T => Unit): Unit = + val it = iterator + while it.hasNext do f(it.next()) + +inline trait Seq[T: Specialized](elems: Array[T]) extends Iterable[T]: + def length: Int = elems.length + def apply(i: Int): T = elems(i) + def iterator: Iterator[T] = new ArrayIterator[T](elems) {} +``` + +This generates the following interface traits (after inlining, conversion to pure interfaces and erasure): + +// TODO: Check that this matches what is actually generated +```scala +inline trait Iterator$sp$Int extends Iterator: + def hasNext: Boolean + def next(): Int + +inline trait ArrayIterator$sp$Int extends ArrayIterator, Iterator$sp$Int + +inline trait Iterable$sp$Int extends Iterable: + def iterator: Iterator$sp$Int + def forall(f: Int => Unit): Unit + +inline trait Seq$sp$Int extends Seq, Iterable$sp$Int: + def length: Int + def apply(i: Int): Int +``` +Note that these traits repeat the parent types of their corresponding inline traits (but with specialization added). For instance, `ArrayIterator$sp$Int` extends the specialized version of its parent `Iterator$sp$Int`, so the specialized trait may be used in contexts expecting: + +- The specialized trait `ArrayIterator$sp$Int` itself (i.e. `ArrayIterator[Int]` in source code) +- A generic `ArrayIterator` (i.e. `ArrayIterator[?]` in source code) +- Specialized traits higher in the specialized hierarchy for example `Iterator$sp$Int`. + +The specialized implementation classes for `ArrayIterator` and `Seq` are as follows (after inlining; iff `new Seq[Int] {}` and `ArrayIterator[Int] {}` are to be found in the program): + +```scala +class ArrayIterator$impl$Int(elems: Array[Int]) extends ArrayIterator$sp$Int, ArrayIterator(elems): + private var current = 0 + override def hasNext: Boolean = + current < elems.length + override def next(): Int = + try elems(current) finally current += 1 + +class Seq$impl$Int(elems: Array[Int]) extends Seq$sp$Int, Seq(elems): + override def iterator: Iterator$sp$Int = new ArrayIterator$impl$Int(elems) + + override def forall(f: Int => Unit): Unit = + val it = iterator + while it.hasNext do f(it.next()) + override def length: Int = elems.length + override def apply(i: Int): Int = elems(i) +``` + +## Transportation of Specialized through generic code + +The `Specialized` Type Class is erased at runtime. Instances of `Specialized[T]` are created automatically for types that do not contain type variables. + +It may surprise you to note that the following is valid. The `Numeric` constraint on `T` is only checked when +a concrete type is provided for `S` (and by extension `T`) when instantiating `T2`. +```scala +inline trait T1[T: Numeric] +inline trait T2[S] extends T1[S] +``` +In constrast, we do not allow this type of behaviour for `Specialized`. This is largely to avoid confusion. In particular: +```scala +inline trait T1[T: Specialized] +inline trait T2[S] extends T1[S] +val x = new T2[Int]() {} +``` +Should `x` be a Specialized `$impl$` instance or a normal anonymous class? If we naively look at just `S`'s definition we would say no, +but this is complicated by the fact that the generated anonymous class will also mixin `T1` directly as well as `T2`. Could we specialize +just for `T1`? This is hard to imagine. Furthermore if we don't specialize, this is also counter intuitive because T1 is then never specialized +to Int, because we have no specialized instance of trait `T2` to specialize into. Therefore we require users to explicitly transport `Specialized` +through their code, in the following way: + +```scala +inline trait T1[T: Specialized] + +inline trait T2 extends T1[List[Int]] // ok +inline trait T3[S] extends T1[List[S]] // error: S should be specialized +inline trait T4[S] extends T1[List[List[S]]] // error: S should be specialized +inline trait T5[S: Specialized] extends T1[List[S]] // ok; should only specialize later +inline trait T6[T[_], S] extends T1[T[S]] // error: T should be specialized // error: S should be specialized +inline trait T7[T[_]] extends T1[T[Int]] // error: T should be specialized + +inline def foo1[S](x: T1[List[S]]): Int = 10 // error: S should be specialized +inline def foo2(x: T1[List[Int]]): Int = 10 // ok +inline def foo3[S](x: T1[List[List[S]]]): Int = 10 // error: S should be specialized +inline def foo4[S: Specialized](x: T1[List[List[S]]]): Int = 10 // ok + +inline def bar1[S] = new T1[List[S]]() {} // error: S should be specialized +inline def bar2 = new T1[List[Int]]() {} // ok +inline def bar3[S] = new T1[List[List[S]]]() {} // error: S should be specialized +inline def bar4[S: Specialized] = new T1[List[List[S]]]() {} // ok +``` +Note that in the cases with `List` this restriction is not imposed because of the definition of `List`, but rather simply because we have a +type variable `S` which is not marked as Specialized which appears `somewhere inside' a type in a Specialized position. Again this is done to +avoid confusion. A user extending `T1[List[S]]` would likely expect some degree of specialization to given the definition of `T1`, but this +is not possible if `S` is not marked as `Specialized`. + +## [1] Why are the generated traits inline? +Consider the following: +```scala +inline trait A[T]: + def foo = "Hello World" +inline trait B extends A[Int] +class C extends B +// inlines to: +class C extends B: + def foo = "Hello World" + +// vs... + +inline trait A[T: Specialized]: + def foo = "Hello World" +inline trait B extends A[Int] +class C extends B +// would expand to: +trait A$sp$Int: + def foo = "Hello World" +inline trait B extends A$sp$Int +class C extends B +``` +We consider the fact that the location of the inlined `foo` method changes with only a simple +addition of `Specialized` to be inconsistent / confusing. Furthermore it would violate the rule +that ordinary traits may not extend inline traits, and causes problems with partial specialization: +```scala +// (1) +inline trait A[T: Specialized, D: Specialized]: + def foo: T + def bar: D +inline trait B[S: Specialized] extends A[S, Int] +trait C extends B[Char] + +// would expand to: +trait A$sp$S$Int[S: Specialized] extends A[S, Int]: // (Ignoring the fact that Specialized may not be used on ordinary traits). + def foo: S + def bar: Int +inline trait B[W: Specialized] extends A$sp$S$Int[W] +trait C extends B[Char] +``` +The definitions are stuck in `A$sp$S$Int$` because it is not inline. This means we can never usefully specialize on `W` even though it is declared `Specialized`. + +Side note: Because we make the generated traits inline, we modify the behaviour of inline traits relative to the original semantics from Timothée's thesis, such that inline traits extended by other inline traits are still inlined (instead of inlining only at the first ordinary class extending the family of inline traits). This is necessary so that `A$sp$S$Int` can be made inline and still contain the specialized declarations which we need when we use it as an interface. The original argument for only inlining at the bottom of the hierarchy was to reduce code generation, and that this was sufficient when we only have inline traits, however the additional code generation is only linear in the number of traits in the sequence (and limited to the interfaces since the implementations are pruned) as we do not inline multiple copies, and we consider this acceptable to implement `Specialized`. + + +## Specialized Traits in the Compiler +We introduce a new phase `desugarSpecializedTraits` responsible for detecting specializations, generating the necessary `$sp$` and `$impl$` +classes for these specializations, and inlining into them. It also replaces `new Vec[Int] {}` with `new Vec$impl$Int`. + +The replacement of references to e.g. `Vec[Int]` with `Vec$sp$Int` is done at erasure, because we cannot change signatures before then, and doing it afterwards would not prevent the boxing that we seek to avoid. + +Specialized traits rely on the semantics and implementation of inline traits, so it may seem logical that `desugarSpecializedTraits` would merely generate +the prototypes for the classes, and allow `specializeInlineTraits` to inline the bodies. We *do not* do this. Rather, the phase `desugarSpecializedTraits` directly performs inlining of the parent traits (`Vec[Int]` in the above example) into the +generated `$sp$` traits and `$impl$` classes, sharing the relevant code with the `specializeInlineTraits` phase through +the `Inlines.scala` file. + +This is done because while we want to keep the two phases separate and avoid coupling where possible (thus allowing e.g. specialized traits to be disabled +while maintaining inline traits), implementing specialized traits requires being able to alternate + between the inlining and specializing steps in some cases. Consider the following +example: + +```scala +inline trait D[R: Specialized] + +inline trait C[S: Specialized]: + def w(y: D[S]): Unit = println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +class B extends A[Char] +``` +If we run just the specialization part of the specialized trait processing (without the inlining yet), we get: + +```scala +inline trait A$sp$Char extends A[Char] +``` + +Inlining the body of `A` results in: +```scala +inline trait A$sp$Char extends A[Char]: + def x(y: C[Char]): Unit = println("x") +``` +Notice that this generates a reference to `C[Char]` which ought to be specialized, given that `S` is marked as `Specialized` in +the definition of `C`. Therefore we need to run the specialization process again. This time we will generate: +```scala +inline trait C$sp$Char extends C[Char] +``` +Inlining the body of `C` into this trait will create a reference to `D[Char]`, which also ought to be specialized. Thus it is clear that +it is possible to create arbitrarily long chains requiring alternating between specialized trait generation and inline trait inlining. We note that +this problem arises not only with the `$sp$` traits, but also the `$impl$` classes (see `specialized-trait-inlining-causes-implementation-required.scala`). + +To resolve this problem without alternating between and looping the `specializeInlineTraits` and `desugarSpecializedTraits` phases in an inconvenient way, we opt to make: +- `specializeInlineTraits` responsible for inlining inline traits written directly in user code. If a specialized trait creates an inline trait inlining opportunity which is not specialized, this is dealt with by specializeInlineTraits. Further if a user writes `class Bar extends Foo[Int]` where Foo is declared Specialized, `specializeInlineTraits` will do the inlining. +- `desugarSpecializedTraits` responsible for finding specializations and generating the required `$sp$` traits and `$impl$` classes, inlining the parent specialized traits into these classes, and repeating until no more inlining can be performed and no more `$sp$` traits and `$impl$` classes are needed. This phase also performs replacement of e.g. `Vec[Int]` with `Vec$sp$Int` and `new Vec[Int]` with `new Vec$impl$Int`. +- `pruneInlineTraits` responsible for converting inline traits to pure interfaces +- We also need to replace members accessed on inline receivers with the corresponding inlined symbols, and this is done in erasure. This is *whether the inline traits in question come from inline traits in source code or specialized trait expansion, in both cases.* (see the document on inline traits for a more detailed description of this operation and `pruneInlineTraits`). + +In particular this decision means that we run `specializeInlineTraits` before `desugarSpecializedTraits`, as otherwise we may duplicate the inlined bodies of the `$sp$` traits and `$impl$` classes, since we have already inlined them in `desugarSpecializedTraits`. + + +To avoid redundant repeated code generation of the same traits and classes, specialized instance traits and classes are cached. The compiler will put their tasty and classfile artifacts in a special directory +on the class path. Each artifact will contain in an annotation a hash of the contents of the trait from which the instance was derived. Before creating a new specialized instance, the compiler will consult this directory to see whether an instance with the given name exists and whether its hash matches. In that case, the artifacts can be re-used. --> + + +// NEED TO PUT THIS WITH THE CLASS BAR EXTENDS FOO PARENTS -> Explain that it's due to pattern match exhaustivity checking for sealed specialized traits +invariant is that if we extend the $sp$ trait we must also extend the original trait. + +## `sealed` Specialized traits +Like other traits, specialized traits may be sealed, but this requires some thought. In particular, the generated $sp$ traits and $impl$ classes extend the sealed specialized trait potentially from another file without the user's consent. We want to allow for example: + +```scala +// A.scala +sealed inline trait Foo[T: Specialized] + +// B.scala +def foo(x: Foo[String]) +``` +because this would be allowed with ordinary traits, but the desugaring creates `Foo$sp$String` which is arguably an illegal child of the sealed `Foo[String]` as it's produced only when compiling B.scala. + +We are also faced with the question of whether we should allow the following: +```scala +// A.scala +sealed inline trait Foo[T: Specialized] +val x = new Foo[Int]() {} // Forces creation of Foo$sp$Int and Foo$impl$Int + +// B.scala +val y = new Foo[Int]() {} +``` +In the bytecode x and y may or may not point to the same `Foo$impl$Int` depending on if we share the generated specialized classes, but in +the source code `y` extends `Foo[Int]` illegally. + +In both cases we essentially opt for the "source code" interpretation as this seems clearest for users. In particular we allow example (1) but not example (2). This happens automatically because `sealed` trait inheritance checking is done before specialized trait desugaring / erasure, so it is unaware of the `$sp$` traits and `$impl$` classes. + +It is tempting to think that since the `$impl$` classes do not exist in source we may be able to allow the second example, but this is dangerous for exhaustivity checking: + +```scala +// A.scala +sealed inline trait Foo[T: Specialized] +inline trait Bar[T: Specialized] extends Foo[T] + +def foo(x: Foo[Int]) = x match { + case y: Bar[Int] => println("All good!") +} + +// B.scala +val y = new Foo[Int]() {} // Bad; A.scala compiled with no warnings as exhaustivity checker assumed this was impossible +foo(y) +val z = new Bar[Int]() {} // Ok: We do allow this as well because Bar is not sealed so the exhaustivity checking in A.scala was correct. +foo(z) + +``` + +In terms of exhaustivity checking, we also want this to work within a single file. Consider: +```scala +sealed inline trait List[+T: Specialized] +sealed inline trait Nill[T: Specialized] extends List[T]: +sealed inline trait :+:[T: Specialized](h: T, t: List[T]) extends List[T] + +val xs: List[Double] = new Nill[Double]() {} + +def foo(x: List[Double]): Unit = x match { + case xs: :+:[_] => f(xs.head); xs.tail.foreach(f) + case _: Nill[_] => + // warning: non-exhaustive pattern match, missing case _: List[Double] +} +``` + +Without any changes, the anonymous class `Nill[Double]` also extends the `List[Double]` interface (because anonymous class instances mixin all ancestor traits), so pattern match exhaustivity checking on `List[Double]` requires `_: List[Double]` because the List trait has anonymous class children. This occurs because we don't do the `$impl$` class replacements until erasure. For that reason we exempt anonymous classes extending specialized traits from being treated as children for pattern match exhaustivity checking, but we do treat the `$impl$` classes as children (the fact that they are defined even if unused suffices for them to be taken into account). This is correct because the anonymous classes will not exist at runtime when the pattern matches run. + +Furthermore, the generated `List$sp$Double` trait also interferes. After we fix this issue: + +```scala +def foo(x: List[Double]): Unit = x match { + case xs: :+:[_] => f(xs.head); xs.tail.foreach(f) + case _: Nill[_] => + // warning: non-exhaustive pattern match, missing case _: List[Double] & List$sp$Double +} +``` +There are two potential solutions to this: +- make `$sp$` traits inherit `sealed` if the original specialized trait is, but this may make future sharing of specializations challenging if we want to extend the specialized traits from another file. +- don't register the `$sp$` traits as children of the original specialized trait for exhaustivity checking. This is safe because these traits are synthetic and we have the invariant that `T <:< Foo[Int] <=> T <:< Foo$sp$Int`, so users cannot match on `Foo$sp$Int` or `Foo[Int] minus Foo$sp$Int` (the latter being empty) + +We opt for the latter. + + + + + + + + + + + + + +Maybe try rewriting as a spec of what we actually do rather than what we wanted to do and see if we get different results! diff --git a/docs/_docs/reference/error-codes/E232.md b/docs/_docs/reference/error-codes/E232.md new file mode 100644 index 000000000000..fa9308eaa006 --- /dev/null +++ b/docs/_docs/reference/error-codes/E232.md @@ -0,0 +1,233 @@ +--- +title: E232: Illegal Use of Specialized Error +kind: Error +since: 3.10.0 +--- +# E232: Illegal Use of Specialized Error + +// TODO: Update since Version field above + +This error occurs when Specialized is used outside of a context bound. + +--- + +## Example + +```scala sc:fail sc-opts:-experimental +def bar(x: Specialized[Int]): Int = ??? // error: Specialized may only be used as a context bound +class Foo(x: Specialized[Int]) // error: Specialized may only be used as a context bound +class Bar(val x: Specialized[Int]) // error: Specialized may only be used as a context bound +val y: Specialized[Char] = ??? // error: Specialized may only be used as a context bound +val v = Specialized.apply[Int] // error: Specialized may only be used as a context bound +def z = println(Specialized.apply[Float]) // error: Specialized may only be used as a context bound +def a = Specialized.apply // error: Specialized may only be used as a context bound +type V = Specialized // error: Specialized may only be used as a context bound +type W = Specialized[Int] // error: Specialized may only be used as a context bound +val b = Specialized // error: Specialized may only be used as a context bound +``` + +### Error + +```scala sc:nocompile +-- [E232] Syntax Error: example.scala:2:13 ------------------------------------- +2 |class Foo(x: Specialized[Int]) // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:3:17 ------------------------------------- +3 |class Bar(val x: Specialized[Int]) // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:1:11 ------------------------------------- +1 |def bar(x: Specialized[Int]): Int = ??? // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:4:7 -------------------------------------- +4 |val y: Specialized[Char] = ??? // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:5:8 -------------------------------------- +5 |val v = Specialized.apply[Int] // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:6:16 ------------------------------------- +6 |def z = println(Specialized.apply[Float]) // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:7:8 -------------------------------------- +7 |def a = Specialized.apply // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:8:5 -------------------------------------- +8 |type V = Specialized // error: Specialized may only be used as a context bound + |^^^^^^^^^^^^^^^^^^^^ + |Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:9:5 -------------------------------------- +9 |type W = Specialized[Int] // error: Specialized may only be used as a context bound + |^^^^^^^^^^^^^^^^^^^^^^^^^ + |Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E232] Syntax Error: example.scala:10:8 ------------------------------------- +10 |val b = Specialized // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |---------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ---------------------------------------------------------------------------- +``` + +### Solution + +Use Specialized correctly, i.e. as a context bound in an inline trait or method: + +```scala sc:compile sc-opts:-experimental +inline trait Vec[T: Specialized](val x: T) + +inline def foo[T: Specialized](v: Vec[T]) = v.x +``` + diff --git a/docs/_docs/reference/error-codes/E233.md b/docs/_docs/reference/error-codes/E233.md new file mode 100644 index 000000000000..6bf92b8ce388 --- /dev/null +++ b/docs/_docs/reference/error-codes/E233.md @@ -0,0 +1,100 @@ +--- +title: E233: Variance in Specialized Traits Limitation +kind: Warning +since: 3.10.0 +--- +# E233: Variance in Specialized Traits Limitation + +// TODO: Update since Version field above + +Emitted when a type parameter of an inline trait is defined both Specialized and contravariant, +to remind the user that this comes with a reduction in expressivity. + +Ordinary contravariance works because of erasure, but specialized traits have a special erasure which prevents +some contravariance patterns involving Object, Any, AnyVal and AnyRef. + +The following example illustrates the problem: + +```scala +inline trait RecyclingBin[-T: Specialized]: + def recycle(x: T) = println(s"Recycling ${x}") + +def recycleAnInteger(bin: RecyclingBin[Int]) = + bin.recycle(100) + +recycleAnInteger(new RecyclingBin[Anyval]() {}) // RecyclingBin[AnyVal] can be interpreted as RecyclingBin[Int] due to contravariance +recycleAnInteger(new RecyclingBin[Any]() {}) // RecyclingBin[Any] can be interpreted as RecyclingBin[Int] due to contravariance + +// Yet, this erases to: + +def recycleAnInteger(bin: RecyclingBin$sp$Int) = + bin.recycle(100) + +recycleAnInteger(new RecyclingBin() {}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin cannot be cast to RecyclingBin$sp$Int +recycleAnInteger(new RecyclingBin() {}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin cannot be cast to RecyclingBin$sp$Int + +``` + +- `RecyclingBin[Any]`, `RecyclingBin[AnyVal]` may not be passed to `RecyclingBin[Int]` whereas normally they would be able to be passed +- `RecyclingBin[Any]`, `RecyclingBin[Object / AnyRef]` may not be passed to RecyclingBin[Paper] whereas normally they would. + +So we impose an additional restriction on contravariance with specialized parameters: +- If `A[F1]` is to be interpreted as `A[F2]` under `A[-T: Specialized]`,we require that `SpecType(F1) = SpecType(F2)`. Given that we also require `F1 >:> F2`, and looking at the definition of SpecType this is roughly equivalent to saying F1 may not be any of the top classes `Any`, `AnyVal`, `AnyRef` unless F2 is also. + +If this restriction is not satisfied, warning E233 is emitted, along with a type error. + +In order that the creator of the contravariant specialized trait is aware of this restriction, the warning E233 is emitted at the point of definition. + +Covariance has a similar problem: +- In general it works fine because it corresponds to interpreting `A[F1]` as `A[F2]` where `F1 <:< F2`. Either`A[F1]` and `A[F2]` both erase to the same type (`A$sp$SpecType(F2)` or `A` if `F1` and `F2` are both top classes), or `A[F1]` erases to `A$sp$F1` and `A[F2]` erases to `A`. But `A$sp$F1` is a subtype of `A` by definition so the upcast will succeed (and upcasts are generally cheap compared to downcasts on the JVM so this is acceptable from a performance perspective). +- The only exception is with `Nothing`, because we want to interpret `A[Nothing]` as e.g. `A[Int]`, but we erase `A[Nothing]` to `A`. `A >:> A$sp$Int` so this doesn't work and we also have to ban it. This makes the code less ergonomic in some cases. For example `case object Nil extends List[Nothing]` has to become `inline trait NilC[T: Specialized] extends List[T]` and `inline def Nil[T: Specialized] = new NilC[T] () {}`, but we don't lose too much expressivity. + +--- + +## Example + +```scala sc:fail sc-opts:-explain,-Werror,-experimental +inline trait Foo[-T: Specialized] // warning: Type parameter is both Specialized and contravariant. +``` + +### Error + +```scala sc:nocompile +-- [E233] Potential Issue Warning: example.scala:1:18 -------------------------- +1 |inline trait Foo[-T: Specialized] // warning: Type parameter is both Specialized and contravariant. + | ^ + |Type parameter is both Specialized and variant. This imposes additional typing restrictions. + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized traits achieve a performance gain through a special erasure. + | - Primitives are specialized: Foo[Int] erases to Foo$sp$Int + | - Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$sp$Animal + | - Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo. + | This means that certain variance patterns that cross these erasure categories will fail at + | runtime due to a ClassCastException, so they are not permitted. + | + | For example, treating Foo[Any] as Foo[Animal] via contravariance is not allowed with Specialized. + | + | Please see the docs for more information on how specialized traits are erased. + | + | If you accept this limitation you can silence this warning with @nowarn. For example: + | + | @nowarn("id=E233") + | inline trait Foo[-T: Specialized]: + | + | Otherwise, remove Specialized, or remove the variance. + | + ----------------------------------------------------------------------------- +``` + +### Solution + +Accept downsides and ignore warning as follows, or remove one of Specialized / variant. + +```scala sc:compile sc-opts:-experimental +import scala.annotation.nowarn +@nowarn("id=E233") +inline trait Foo[-T: Specialized] +``` + diff --git a/docs/_docs/reference/experimental/inline-traits.md b/docs/_docs/reference/experimental/inline-traits.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/_docs/reference/experimental/specialized-traits.md b/docs/_docs/reference/experimental/specialized-traits.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/sidebar.yml b/docs/sidebar.yml index ac52b8c4d5ab..125eabae644d 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -195,6 +195,8 @@ subsection: - page: reference/experimental/quoted-patterns-with-polymorphic-functions.md - page: reference/experimental/relaxed-lambdas.md - page: reference/experimental/sub-cases.md + - page: reference/experimental/inline-traits.md + - page: reference/experimental/specialized-traits.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md @@ -437,3 +439,5 @@ subsection: - page: reference/error-codes/E229.md - page: reference/error-codes/E230.md - page: reference/error-codes/E231.md + - page: reference/error-codes/E232.md + - page: reference/error-codes/E233.md diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala index c164b2b4962e..4b9edcf8faa5 100644 --- a/library/src/scala/language.scala +++ b/library/src/scala/language.scala @@ -243,6 +243,13 @@ object language { @compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements") object erasedDefinitions + /** Experimental support for specialized traits + * + * @see [[docs/_docs/internals/specialized-traits.md]] + */ + @compileTimeOnly("`specializedTraits` can only be used at compile time in import statements") + object specializedTraits + /** Experimental support for relaxed CanEqual checks for ADT pattern matching * * @see [[https://github.com/scala/improvement-proposals/pull/97]] diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index f380a6913815..d001ba729f8d 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -52,6 +52,9 @@ private[scala] object language: @compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements") object erasedDefinitions + @compileTimeOnly("`specializedTraits` can only be used at compile time in import statements") + object specializedTraits + /** Experimental support for relaxed CanEqual checks for ADT pattern matching * * @see [[https://github.com/scala/improvement-proposals/pull/97]] diff --git a/library/src/scala/specialize/Specialized.scala b/library/src/scala/specialize/Specialized.scala new file mode 100644 index 000000000000..a2f70cc43a1d --- /dev/null +++ b/library/src/scala/specialize/Specialized.scala @@ -0,0 +1,10 @@ +package scala.specialize +import language.experimental.erasedDefinitions +import scala.annotation.nowarn + +sealed trait Specialized[T] extends compiletime.Erased + +object Specialized: + /* @nowarn: New anonymous class definition will be duplicated at each inline site, + however it's erased at runtime so we don't care. */ + @nowarn inline def apply[T] = new Specialized[T] {} diff --git a/project/scripts/checkSidebarDocs.scala b/project/scripts/checkSidebarDocs.scala index 82a4d13d8982..243397ebd4ce 100644 --- a/project/scripts/checkSidebarDocs.scala +++ b/project/scripts/checkSidebarDocs.scala @@ -18,6 +18,8 @@ val internalsIgnored = Set[String]( "internals/cc/alternatives-to-sealed.md", "internals/cc/handling-invariant-vars.md", "internals/exclusive-capabilities.md", + "internals/inline-traits.md", + "internals/specialized-traits.md", ) @main def checkSidebarDocs(): Unit = { diff --git a/tests/disabled/pos/inline-trait-4-inner-class.scala b/tests/disabled/pos/inline-trait-4-inner-class.scala new file mode 100644 index 000000000000..b3c25e75de4c --- /dev/null +++ b/tests/disabled/pos/inline-trait-4-inner-class.scala @@ -0,0 +1,20 @@ +inline trait Options[+T]: + sealed trait Option: + def get: T + def isEmpty: Boolean + + class Some(x: T) extends Option: + def get: T = x + def isEmpty: Boolean = false + + object None extends Option: + def get: T = throw new NoSuchElementException("None.get") + def isEmpty: Boolean = true +end Options + +object IntOptions extends Options[Int] +import IntOptions._ + +val o1: Option = Some(1) // specialized +val o2: Option = None +val x1: Int = o1.get // no unboxing diff --git a/tests/disabled/pos/inline-trait-body-class-abstract.scala b/tests/disabled/pos/inline-trait-body-class-abstract.scala new file mode 100644 index 000000000000..4704b324e26d --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-class-abstract.scala @@ -0,0 +1,10 @@ +inline trait A: + class InnerA: + def foo(): Int + def bar = foo() + 1 + +class B extends A: + class InnerB extends InnerA: + def foo(): Int = -23 + + def f = InnerB().bar \ No newline at end of file diff --git a/tests/disabled/pos/inline-trait-body-class-enum.scala b/tests/disabled/pos/inline-trait-body-class-enum.scala new file mode 100644 index 000000000000..a114ff396067 --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-class-enum.scala @@ -0,0 +1,6 @@ +inline trait A: + enum Inner: + case A, B, C + +class B extends A: + def f = Inner.B \ No newline at end of file diff --git a/tests/disabled/pos/inline-trait-body-class-object.scala b/tests/disabled/pos/inline-trait-body-class-object.scala new file mode 100644 index 000000000000..dc990c69573c --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-class-object.scala @@ -0,0 +1,6 @@ +inline trait A[T]: + object Inner: + val x: T = ??? + +class B extends A[Int]: + def i: Int = Inner.x diff --git a/tests/disabled/pos/inline-trait-body-trait-generic.scala b/tests/disabled/pos/inline-trait-body-trait-generic.scala new file mode 100644 index 000000000000..999dd0c8c1ea --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-trait-generic.scala @@ -0,0 +1,6 @@ +inline trait A[T]: + trait InnerA[U]: + def x: (T, U) = ??? + +class B extends A[Int]: + class InnerB extends InnerA[String] \ No newline at end of file diff --git a/tests/neg/i2421.scala b/tests/neg/i2421.scala index dc8e229f38f0..bcf3da6dbb36 100644 --- a/tests/neg/i2421.scala +++ b/tests/neg/i2421.scala @@ -1,7 +1,6 @@ inline object Foo // OK (error would be detected later, in PostTyper) inline class Bar // error: modifier(s) `inline' incompatible with type definition inline abstract class Baz // error: modifier(s) `inline' incompatible with type definition -inline trait Qux // error: modifier(s) `inline' incompatible with type definition object Quux { inline type T // error: modifier(s) `inline' incompatible with type definition diff --git a/tests/neg/i8337.scala b/tests/neg/i8337.scala index 6e42b96c2855..3f05d00d2d4f 100644 --- a/tests/neg/i8337.scala +++ b/tests/neg/i8337.scala @@ -3,4 +3,4 @@ class Bar extends Foo[Bar] object Q { // error: cyclic reference opaque type X <: Foo[X] = Bar // error: cyclic reference -} \ No newline at end of file +} diff --git a/tests/neg/inline-trait-body-class-case.scala b/tests/neg/inline-trait-body-class-case.scala new file mode 100644 index 000000000000..20a58fd4b267 --- /dev/null +++ b/tests/neg/inline-trait-body-class-case.scala @@ -0,0 +1,5 @@ +inline trait A: + case class Inner(val x: Int) // error + +class B extends A: + def f = Inner(17).x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-extends-inline-trait.scala b/tests/neg/inline-trait-body-class-extends-inline-trait.scala new file mode 100644 index 000000000000..0e52135b3bd6 --- /dev/null +++ b/tests/neg/inline-trait-body-class-extends-inline-trait.scala @@ -0,0 +1,11 @@ +inline trait A: + class Inner extends Trait[Int]: // error: Inline traits may not define inner classes or traits. + val x = 1 + +inline trait Trait[T]: + def f(x: T): T = x + +class B extends A: + val inner = Inner() + def x = inner.x + def f = inner.f(x) \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-generic.scala b/tests/neg/inline-trait-body-class-generic.scala new file mode 100644 index 000000000000..bd0fc5f3de42 --- /dev/null +++ b/tests/neg/inline-trait-body-class-generic.scala @@ -0,0 +1,6 @@ +inline trait A[T]: + class Inner[U](u: U): // error + val x: (T, U) = (???, u) + def f: (T, String) = Inner("U").x + +class B extends A[Int] \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-params.scala b/tests/neg/inline-trait-body-class-params.scala new file mode 100644 index 000000000000..e60da2536a69 --- /dev/null +++ b/tests/neg/inline-trait-body-class-params.scala @@ -0,0 +1,5 @@ +inline trait A: + class Inner(val x: Int) // error + +class B extends A: + def f = Inner(17).x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-return.scala b/tests/neg/inline-trait-body-class-return.scala new file mode 100644 index 000000000000..8e3bbd32e3d7 --- /dev/null +++ b/tests/neg/inline-trait-body-class-return.scala @@ -0,0 +1,7 @@ +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 1 + def generate(x: Int) = new InnerA() {} + +class B extends A: + val y = generate(7) diff --git a/tests/neg/inline-trait-body-class-sealed.scala b/tests/neg/inline-trait-body-class-sealed.scala new file mode 100644 index 000000000000..737e36fba960 --- /dev/null +++ b/tests/neg/inline-trait-body-class-sealed.scala @@ -0,0 +1,7 @@ +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 1 + +class B extends A: + class InnerB extends InnerA + def f = InnerB().x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-simple.scala b/tests/neg/inline-trait-body-class-simple.scala new file mode 100644 index 000000000000..8c3ee234310a --- /dev/null +++ b/tests/neg/inline-trait-body-class-simple.scala @@ -0,0 +1,6 @@ +inline trait A: + class Inner: // error: Inline traits may not define inner classes or traits. + val x = 1 + +class B extends A: + def f = Inner().x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-override-def-final.scala b/tests/neg/inline-trait-body-override-def-final.scala new file mode 100644 index 000000000000..c67540a09924 --- /dev/null +++ b/tests/neg/inline-trait-body-override-def-final.scala @@ -0,0 +1,5 @@ +inline trait A: + final def f(x: Int) = x + +class B extends A: + override final def f(x: Int) = x + 1 // error \ No newline at end of file diff --git a/tests/neg/inline-trait-body-override-val-final.scala b/tests/neg/inline-trait-body-override-val-final.scala new file mode 100644 index 000000000000..32d20c4b6696 --- /dev/null +++ b/tests/neg/inline-trait-body-override-val-final.scala @@ -0,0 +1,5 @@ +inline trait A: + final val x = 1 + +class B extends A: + override final val x = 2 // error \ No newline at end of file diff --git a/tests/neg/inline-trait-body-override-var.scala b/tests/neg/inline-trait-body-override-var.scala new file mode 100644 index 000000000000..4ad8b79db477 --- /dev/null +++ b/tests/neg/inline-trait-body-override-var.scala @@ -0,0 +1,5 @@ +inline trait A: + var x: Int = 1 + +class B extends A: + override var x = 2 // error diff --git a/tests/neg/inline-trait-body-private-name-collision.scala b/tests/neg/inline-trait-body-private-name-collision.scala new file mode 100644 index 000000000000..c0f6624fd707 --- /dev/null +++ b/tests/neg/inline-trait-body-private-name-collision.scala @@ -0,0 +1,3 @@ +inline trait A: + private val x: Int = 1 // error: inline traits cannot have non-local private members + def eq(o: A) = o.x == x diff --git a/tests/neg/inline-trait-body-trait-inline.scala b/tests/neg/inline-trait-body-trait-inline.scala new file mode 100644 index 000000000000..82144e4c9c4d --- /dev/null +++ b/tests/neg/inline-trait-body-trait-inline.scala @@ -0,0 +1,7 @@ +inline trait A[T]: + inline trait InnerA[U]: // error + val x: (T, U) = ??? + +class B extends A[Int]: + class InnerB extends InnerA[String] + def f: (Int, String) = InnerB().x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-trait-parameter.scala b/tests/neg/inline-trait-body-trait-parameter.scala new file mode 100644 index 000000000000..ec11c5b9052e --- /dev/null +++ b/tests/neg/inline-trait-body-trait-parameter.scala @@ -0,0 +1,7 @@ +inline trait A[T]: + trait InnerAType[T >: Int <: AnyVal] + trait InnerATypes[T <: AnyVal, U <: T] + trait InnerATerm(i: Int) // error + trait InnerATerms(i: Int, j: Double) // error + trait InnerATermsCurried(i: Int, j: Double)(k: String) // error + trait InnerAAllCurried[T, U](i: T, j: U)(k: (T, U)) // error \ No newline at end of file diff --git a/tests/neg/inline-trait-body-trait-simple.scala b/tests/neg/inline-trait-body-trait-simple.scala new file mode 100644 index 000000000000..91b5bc380543 --- /dev/null +++ b/tests/neg/inline-trait-body-trait-simple.scala @@ -0,0 +1,6 @@ +inline trait A[T]: + trait InnerA: // error: Inline traits may not contain inner classes. + def x: T = ??? + +class B extends A[Int]: + class InnerB extends InnerA \ No newline at end of file diff --git a/tests/neg/inline-trait-body-trait-term-parameters.scala b/tests/neg/inline-trait-body-trait-term-parameters.scala new file mode 100644 index 000000000000..ef7c96baf6c4 --- /dev/null +++ b/tests/neg/inline-trait-body-trait-term-parameters.scala @@ -0,0 +1,7 @@ + +inline trait A[T]: + trait InnerA(t: T): // error + def x: T = t + +class B extends A[Int]: + class InnerB extends InnerA(???) \ No newline at end of file diff --git a/tests/neg/inline-trait-child-overrides-parent-missing-override.scala b/tests/neg/inline-trait-child-overrides-parent-missing-override.scala new file mode 100644 index 000000000000..18aabebbcde0 --- /dev/null +++ b/tests/neg/inline-trait-child-overrides-parent-missing-override.scala @@ -0,0 +1,5 @@ + +inline trait A(val x: Int) + +class C extends A(10): + val x = 1000 // error: Needs override modifier diff --git a/tests/neg/inline-trait-clash-method-method-needs-override.scala b/tests/neg/inline-trait-clash-method-method-needs-override.scala new file mode 100644 index 000000000000..61e94c7f5020 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-method-needs-override.scala @@ -0,0 +1,9 @@ +inline trait A: + def x(y: String) = "Hello world" + +class C extends A: + def x(y: String) = "Hello world2" // error: Needs override + +@main def Test = + val v = C() + assert(v.x("Hello World") == "Hello world2") diff --git a/tests/neg/inline-trait-clash-method-param.scala b/tests/neg/inline-trait-clash-method-param.scala new file mode 100644 index 000000000000..2142bf3f4a6b --- /dev/null +++ b/tests/neg/inline-trait-clash-method-param.scala @@ -0,0 +1,11 @@ +inline trait A: + def x(b: Int) = "Hello world" + def z = "Hello world" + +class C(x: String, z: String) extends A: // error: Inlining of inline trait created name conflict on z. + val y = x + val w = z + +@main def Test = + val v = C("Overridden", "Overridden2") + assert(v.y == "Overridden") diff --git a/tests/neg/inline-trait-clash-method-val-param-needs-override.scala b/tests/neg/inline-trait-clash-method-val-param-needs-override.scala new file mode 100644 index 000000000000..82d7d0093972 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-val-param-needs-override.scala @@ -0,0 +1,9 @@ +inline trait A: + def x = "Hello world" + +class C extends A: + val x = "Overridden" // error: Needs override + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/neg/inline-trait-clash-method-var-param-missing-override.scala b/tests/neg/inline-trait-clash-method-var-param-missing-override.scala new file mode 100644 index 000000000000..70077210d794 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-var-param-missing-override.scala @@ -0,0 +1,9 @@ +inline trait A: + def x = "Hello world" + +class C extends A: + var x = "Overridden" // error: Needs override + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/neg/inline-trait-clash-method-var-param.scala b/tests/neg/inline-trait-clash-method-var-param.scala new file mode 100644 index 000000000000..18caa31af679 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-var-param.scala @@ -0,0 +1,9 @@ +inline trait A: + def x = "Hello world" + +class C extends A: + override var x = "Overridden" // error: Setter x_= overrides nothing + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/neg/inline-trait-clash-val-param-method-2.scala b/tests/neg/inline-trait-clash-val-param-method-2.scala new file mode 100644 index 000000000000..a7a87b4d8bf7 --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-method-2.scala @@ -0,0 +1,4 @@ +inline trait A(val x: Int) + +class C extends A(10): + def x = 1000 // error: Needs override marker diff --git a/tests/neg/inline-trait-clash-val-param-method.scala b/tests/neg/inline-trait-clash-val-param-method.scala new file mode 100644 index 000000000000..697b82f28721 --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-method.scala @@ -0,0 +1,4 @@ +inline trait A(val x: Int) + +class D extends A(10): + override def x = 1000 // error: needs to be a stable, immutable value diff --git a/tests/neg/inline-trait-clash-val-param-param.scala b/tests/neg/inline-trait-clash-val-param-param.scala new file mode 100644 index 000000000000..b3225b0d93fd --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-param.scala @@ -0,0 +1,15 @@ +// Not allowed due to name clash + +inline trait A(val x: Int): + val y = x + +class C(x: Int) extends A(10): // error: Inlining of inline trait created name conflict on x. Constructor parameters of inline receivers may not collide with members of inline traits. + val z = x + +@main def Test = + val v = C(5) + assert(v.y == 10) + assert(v.x == 10) + assert(v.z == 5) + println(v.y) + println(v.x) diff --git a/tests/neg/inline-trait-clash-val-param-val-param-missing-override.scala b/tests/neg/inline-trait-clash-val-param-val-param-missing-override.scala new file mode 100644 index 000000000000..917e0dbb3d8d --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-val-param-missing-override.scala @@ -0,0 +1,4 @@ + +inline trait A(val x: Int, val y: Int) +class C(val y: Int) extends A(10, 54): // error: Needs override + val x = 1000 // error: Needs override diff --git a/tests/neg/inline-trait-clash-var-param-method-2.scala b/tests/neg/inline-trait-clash-var-param-method-2.scala new file mode 100644 index 000000000000..494e30d9dd52 --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-method-2.scala @@ -0,0 +1,4 @@ +inline trait A(var x: Int) + +class D extends A(10): + override def x = 1000 // error: cannot override a mutable value diff --git a/tests/neg/inline-trait-clash-var-param-method.scala b/tests/neg/inline-trait-clash-var-param-method.scala new file mode 100644 index 000000000000..8b90379516b4 --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-method.scala @@ -0,0 +1,4 @@ +inline trait A(var x: Int) + +class C extends A(10): + def x = 1000 // error: Needs override marker diff --git a/tests/neg/inline-trait-clash-var-param-param.scala b/tests/neg/inline-trait-clash-var-param-param.scala new file mode 100644 index 000000000000..3147ebfeb368 --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-param.scala @@ -0,0 +1,15 @@ +// Not allowed due to name clash + +inline trait A(var x: Int): + val y = x + +class C(x: Int) extends A(10): // error: Inlining of inline trait created name conflict on x. Constructor parameters of inline receivers may not collide with members of inline traits. + val z = x + +@main def Test = + val v = C(5) + assert(v.y == 10) + assert(v.x == 10) + assert(v.z == 5) + println(v.y) + println(v.x) diff --git a/tests/neg/inline-trait-clash-var-param-var-param.scala b/tests/neg/inline-trait-clash-var-param-var-param.scala new file mode 100644 index 000000000000..64173b7321a6 --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-var-param.scala @@ -0,0 +1,8 @@ +inline trait A(val x: Int, var y: Int) +class C(override var y: Int) extends A(10, 54): // error: Cannot override a mutable variable + override val x = 1000 + +@main def Test = + val v = C(44) + assert(v.x == 1000) + assert(v.y == 44) diff --git a/tests/neg/inline-trait-clashing-parent-members.scala b/tests/neg/inline-trait-clashing-parent-members.scala new file mode 100644 index 000000000000..e217baf4c70a --- /dev/null +++ b/tests/neg/inline-trait-clashing-parent-members.scala @@ -0,0 +1,11 @@ +inline trait A: + val x = 10 + +inline trait B: + val x = 11 + +class C extends A, B // error: C inherits conflicting members x from A and B + +@main def Test = + val v = C() + assert(v.x == 11) diff --git a/tests/neg/inline-trait-clashing-parent-methods.scala b/tests/neg/inline-trait-clashing-parent-methods.scala new file mode 100644 index 000000000000..002014e88417 --- /dev/null +++ b/tests/neg/inline-trait-clashing-parent-methods.scala @@ -0,0 +1,7 @@ +inline trait A: + def foo = "Hello World" + +inline trait B: + def foo = "Bonjour" + +class C extends A, B // error: C inherits conflicting members diff --git a/tests/neg/inline-trait-clashing-parent-val-params.scala b/tests/neg/inline-trait-clashing-parent-val-params.scala new file mode 100644 index 000000000000..e803c42d9312 --- /dev/null +++ b/tests/neg/inline-trait-clashing-parent-val-params.scala @@ -0,0 +1,9 @@ +inline trait A(val x: Int) + +inline trait B(val x: Int) + +class C extends A(10), B(11) // error: C inherits conflicting members + +@main def Test = + val v = C() + assert(v.x == 11) diff --git a/tests/neg/inline-trait-co-nested.scala b/tests/neg/inline-trait-co-nested.scala new file mode 100644 index 000000000000..fb600bcdd3a0 --- /dev/null +++ b/tests/neg/inline-trait-co-nested.scala @@ -0,0 +1,9 @@ +inline trait A: // At the moment this works with an ordinary trait but throws a TypeError with inline traits + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = new InnerB + + sealed class InnerB: // error: Inline traits may not define inner classes or traits. + val x = new InnerA + +class B extends A: + val y = 10 diff --git a/tests/neg/inline-trait-cross-reference-defined-after.scala b/tests/neg/inline-trait-cross-reference-defined-after.scala new file mode 100644 index 000000000000..eb3fb51f4450 --- /dev/null +++ b/tests/neg/inline-trait-cross-reference-defined-after.scala @@ -0,0 +1,9 @@ +inline trait A: // At the moment this works with an ordinary trait but throws a TypeError with inline traits + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = new InnerB + + sealed class InnerB: // error: Inline traits may not define inner classes or traits. + val x = 10 + +class B extends A: + val y = 10 diff --git a/tests/neg/inline-trait-double-nested-class.scala b/tests/neg/inline-trait-double-nested-class.scala new file mode 100644 index 000000000000..c8352791bfb6 --- /dev/null +++ b/tests/neg/inline-trait-double-nested-class.scala @@ -0,0 +1,13 @@ +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + sealed class InnerInnerA: + val x = 1 + +class B extends A: + class InnerB extends InnerA { + class InnerInnerB extends InnerInnerA + } + def f = + val a = new InnerB() + val b = a.InnerInnerB() + b.x diff --git a/tests/neg/inline-trait-infinite-inline-triangle-cycle.scala b/tests/neg/inline-trait-infinite-inline-triangle-cycle.scala new file mode 100644 index 000000000000..2f506c96871f --- /dev/null +++ b/tests/neg/inline-trait-infinite-inline-triangle-cycle.scala @@ -0,0 +1,9 @@ +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S] extends C[S] + +class Cl[T] extends D[T] // error: Inlining of inline traits looped, which will create an infinitely long program. This is not allowed. diff --git a/tests/neg/inline-trait-inline-overrides-non-inline-retained-inline.scala b/tests/neg/inline-trait-inline-overrides-non-inline-retained-inline.scala new file mode 100644 index 000000000000..3d400831c81b --- /dev/null +++ b/tests/neg/inline-trait-inline-overrides-non-inline-retained-inline.scala @@ -0,0 +1,9 @@ +inline trait A: + def x: Int + +inline trait B extends A: + override inline def x = 10 // error: implementation restriction: inline traits cannot have non-local private members. This also means no retained inline methods. + +@main def Test = + val x: A = new B() {} + assert(x.x == 10) diff --git a/tests/neg/inline-trait-inline-val-constant-value.scala b/tests/neg/inline-trait-inline-val-constant-value.scala new file mode 100644 index 000000000000..7984686e9d98 --- /dev/null +++ b/tests/neg/inline-trait-inline-val-constant-value.scala @@ -0,0 +1,2 @@ +inline trait A[T](x: T): + inline val property = x // error: inline value must have a literal constant type diff --git a/tests/neg/inline-trait-nested-class-outside-ref.scala b/tests/neg/inline-trait-nested-class-outside-ref.scala new file mode 100644 index 000000000000..6a432cec3ecf --- /dev/null +++ b/tests/neg/inline-trait-nested-class-outside-ref.scala @@ -0,0 +1,11 @@ +inline trait A: + class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 10 + +class B extends A: + def foo = 10 + +def x = + val b = B() + val c = b.InnerA() + c diff --git a/tests/neg/inline-trait-nested-class-parameter-passing.scala b/tests/neg/inline-trait-nested-class-parameter-passing.scala new file mode 100644 index 000000000000..651e306e1089 --- /dev/null +++ b/tests/neg/inline-trait-nested-class-parameter-passing.scala @@ -0,0 +1,18 @@ +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 1 + +class B extends A: + class InnerB extends InnerA: + override val x = 2 + +def foo(x: A#InnerA) = println(x.x) + +@main def main = + val a = new A() {} + val inner_a = a.InnerA() + foo(inner_a) + + val b = B() + val inner_b = b.InnerB() + foo(inner_b) diff --git a/tests/neg/inline-trait-opaque-type-fail.scala b/tests/neg/inline-trait-opaque-type-fail.scala new file mode 100644 index 000000000000..126404829191 --- /dev/null +++ b/tests/neg/inline-trait-opaque-type-fail.scala @@ -0,0 +1,14 @@ +inline trait A[T](val x: T): + opaque type Special = T + type Ordinary = T + opaque type Special2 = Int + + def foo1: Special = 10 // error: 10 does not conform to T + def bar1: Ordinary = 10 // error: 10 does not conform to T + def baz1: Special2 = 10 // This one is fine + +class B extends A[Int](100): + def foo2: Special = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + def bar2: Ordinary = 10 // This one is fine + def baz2: Special2 = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + \ No newline at end of file diff --git a/tests/neg/inline-trait-override-private-member-deleted.scala b/tests/neg/inline-trait-override-private-member-deleted.scala new file mode 100644 index 000000000000..6ef7c6c0ee8d --- /dev/null +++ b/tests/neg/inline-trait-override-private-member-deleted.scala @@ -0,0 +1,5 @@ +inline trait Foo: + private def foo = 10 + +class A extends Foo: + override def foo = 11 // error: method foo overrides nothing diff --git a/tests/neg/inline-trait-parent-trait-param-access.scala b/tests/neg/inline-trait-parent-trait-param-access.scala new file mode 100644 index 000000000000..e283a5f90bd1 --- /dev/null +++ b/tests/neg/inline-trait-parent-trait-param-access.scala @@ -0,0 +1,4 @@ +inline trait A(x: Int) + +class C extends A(10): + val y = x // error: Not Found Error diff --git a/tests/neg/inline-trait-self-inline-anonymous-class.scala b/tests/neg/inline-trait-self-inline-anonymous-class.scala new file mode 100644 index 000000000000..b916ac476992 --- /dev/null +++ b/tests/neg/inline-trait-self-inline-anonymous-class.scala @@ -0,0 +1,7 @@ +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +class T extends C[Int] // error: Inlining of inline traits looped. diff --git a/tests/neg/inline-trait-self-inline-three-cycle.scala b/tests/neg/inline-trait-self-inline-three-cycle.scala new file mode 100644 index 000000000000..4f54945e81ec --- /dev/null +++ b/tests/neg/inline-trait-self-inline-three-cycle.scala @@ -0,0 +1,20 @@ +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new E[S] {} + println("w") + +inline trait E[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +def main = + val x = new C[Int] {} // error: Inlining of inline traits looped. diff --git a/tests/neg/inline-trait-self-inline-two-cycle.scala b/tests/neg/inline-trait-self-inline-two-cycle.scala new file mode 100644 index 000000000000..98da6c2455a1 --- /dev/null +++ b/tests/neg/inline-trait-self-inline-two-cycle.scala @@ -0,0 +1,14 @@ +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +def main = + val x = new C[Int] {} // error: Inlining of inline traits looped. diff --git a/tests/neg/inline-trait-self-specialization-loop.scala b/tests/neg/inline-trait-self-specialization-loop.scala new file mode 100644 index 000000000000..6c986eb241dc --- /dev/null +++ b/tests/neg/inline-trait-self-specialization-loop.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[Int] {} + println("w") + +def main = + val y = new C[Int] {} // error: Inlining of inline traits loops which would create an infinitely long program. This is not allowed. diff --git a/tests/neg/inline-trait-self-type-missing-parent.scala b/tests/neg/inline-trait-self-type-missing-parent.scala new file mode 100644 index 000000000000..33b781ac026b --- /dev/null +++ b/tests/neg/inline-trait-self-type-missing-parent.scala @@ -0,0 +1,6 @@ +trait T1 +inline trait A[T]: + this: T1 => + +class B extends A[Int] // error: illegal inheritance: class B does not conform to self type +class C extends A, T1 diff --git a/tests/neg/inline-trait-self-type-problems.scala b/tests/neg/inline-trait-self-type-problems.scala new file mode 100644 index 000000000000..317620f27fbd --- /dev/null +++ b/tests/neg/inline-trait-self-type-problems.scala @@ -0,0 +1,23 @@ +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T]: + this: T1 => + +inline trait D extends A[Int] // error: self type of D does not conform to that of A +inline trait E extends D + +inline trait B[T]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int] // error: self type of F does not conform to that of A + +inline trait C[T]: + this: T => + +inline trait H extends C[Test]: // error self type of H does not conform to that of C + this: T3 => + +class Cl2 extends Test with H with T3 diff --git a/tests/neg/inline-trait-supercall-into-trait.scala b/tests/neg/inline-trait-supercall-into-trait.scala new file mode 100644 index 000000000000..1c71df5cc554 --- /dev/null +++ b/tests/neg/inline-trait-supercall-into-trait.scala @@ -0,0 +1,7 @@ +trait A: + def foo = "A" + +inline trait B extends A: + override def foo = super.foo // error: Inline traits may not contain superclass references to classes or non-inline traits + +class C1 extends B diff --git a/tests/neg/inline-trait-uninitialized-value-should-warn.scala b/tests/neg/inline-trait-uninitialized-value-should-warn.scala new file mode 100644 index 000000000000..2a605f38a71f --- /dev/null +++ b/tests/neg/inline-trait-uninitialized-value-should-warn.scala @@ -0,0 +1,7 @@ +//> using options -Werror -Wsafe-init + +inline trait A(val x: Int): + val f = z + val z: Int // nopos-error: This should warn with -Wsafe-init +class C extends A(10): + val z = 10 diff --git a/tests/neg/specialized-trait-anonymous-class-breaks-rules.scala b/tests/neg/specialized-trait-anonymous-class-breaks-rules.scala new file mode 100644 index 000000000000..0ef65ba4c3dd --- /dev/null +++ b/tests/neg/specialized-trait-anonymous-class-breaks-rules.scala @@ -0,0 +1,27 @@ +//> using options -language:experimental.specializedTraits + +inline trait Baz[T: Specialized]: + def foo = "hello world" + +inline trait Foo[T: Specialized] extends Baz[T]: + val bar = 10 + +inline trait Bar[T: Specialized]: + val baz = 100 + +trait MyTrait[T](val x: T) + +@main def main = + val w = new Foo[Int] {} // ok + + val x = new Foo[Int] { // error: Anonymous classes acting as instances of Specialized traits may not have additional members; you can make a named object instead if you like. + val y = 10 + } + + val y = new Foo[Int] with MyTrait(10) with Bar {} // error: Anonymous classes acting as instances of Specialized traits may not mix in other traits; you can make a named object instead if you like. + + val z = new Foo[Int] with Bar {} // error: Anonymous classes acting as instances of Specialized traits may not mix in other traits; you can make a named object instead if you like. + + val a = new Foo[Int] with Baz[Int] {} // ok: We have to allow this because it desugars to the same thing as the new Foo[Int] {} case; it will have the same behaviour when compiled; + + val b = new Baz[Int] with Foo[Int] {} // ok: We have to allow this because it desugars to the same thing as the new Foo[Int] {} case; it will have the same behaviour when compiled; diff --git a/tests/neg/specialized-trait-banned-covariance.scala b/tests/neg/specialized-trait-banned-covariance.scala new file mode 100644 index 000000000000..4afee41a7617 --- /dev/null +++ b/tests/neg/specialized-trait-banned-covariance.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait List[+T: Specialized] +case object Nil extends List[Nothing] + +def foo(x: List[Int]) = println("Foo") + +def main = + foo(Nil) // error: This use of variance is incompatible with specialized traits. diff --git a/tests/neg/specialized-trait-drops-specialized-qualifier.scala b/tests/neg/specialized-trait-drops-specialized-qualifier.scala new file mode 100644 index 000000000000..5cc6d272a1c0 --- /dev/null +++ b/tests/neg/specialized-trait-drops-specialized-qualifier.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] +inline trait T2[S] extends T1[S] // error: type S used in a Specialized position, so it must be marked as Specialized at its definition. + +inline def foo[E](x: T1[E]) = 100 // error: type E used in a Specialized position, so it must be marked as Specialized at its definition. + +inline def foo[F] = new T1[F]() {} // error: type F used in a Specialized position, so it must be marked as Specialized at its definition. + +class Bar[T] extends T1[T] // error: type T used in a Specialized position, so it must be marked Specialized at its definition diff --git a/tests/neg/specialized-trait-exhaustivity-check-2.scala b/tests/neg/specialized-trait-exhaustivity-check-2.scala new file mode 100644 index 000000000000..e77c49d546eb --- /dev/null +++ b/tests/neg/specialized-trait-exhaustivity-check-2.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits -Werror + +// nopos-error: (warning) non-exhaustive pattern match + +sealed inline trait Foo[S: Specialized] +sealed inline trait Baz[S: Specialized] extends Foo[S] +inline trait Bar extends Foo[Int] + +def doMatch(x: Foo[Int]) = x match { + case x: Baz[_] => println("Hello World") +} + + diff --git a/tests/neg/specialized-trait-exhaustivity-check.scala b/tests/neg/specialized-trait-exhaustivity-check.scala new file mode 100644 index 000000000000..0d6d3c8b9889 --- /dev/null +++ b/tests/neg/specialized-trait-exhaustivity-check.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits -Werror + +// nopos-error: (warning) non-exhaustive pattern match + +sealed inline trait Foo[S: Specialized] +inline trait Bar extends Foo[Int] + +def doMatch(x: Foo[Int]) = x match { + case x: Bar => println("Hello World") +} + +def main = + val x = new Foo[Int]() {} + doMatch(x) + + diff --git a/tests/neg/specialized-trait-infinite-inline.scala b/tests/neg/specialized-trait-infinite-inline.scala new file mode 100644 index 000000000000..37a8d0c09db8 --- /dev/null +++ b/tests/neg/specialized-trait-infinite-inline.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized]: + inline def a[S: Specialized](b: S): Boolean = a[S](b) + +@main def main = + val x = new Foo[Int]() {} + x.a(100) // error: inline limit exceeded diff --git a/tests/neg/specialized-trait-inline-method-missing-specialized.scala b/tests/neg/specialized-trait-inline-method-missing-specialized.scala new file mode 100644 index 000000000000..0b3f9fffb882 --- /dev/null +++ b/tests/neg/specialized-trait-inline-method-missing-specialized.scala @@ -0,0 +1,2 @@ +inline trait Vec[T: Specialized](val xs: List[T]): + def method[S: Specialized](other: Vec[S]): Vec[(T, S)] // error: Only inline traits and inline functions may take specialized type parameters diff --git a/tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala b/tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala new file mode 100644 index 000000000000..5201e6a602e3 --- /dev/null +++ b/tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala @@ -0,0 +1,23 @@ +//> using options -language:experimental.specializedTraits + +// Contrast with specialized-trait-inlining-causes-implementation-required-loop-bad.scala. +// and specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala +// This one is not allowed because it will loop forever when specializing. The $impl$ class will +// get a loop when the body of w is inlined. + +// A[Char] => A$sp$Char, but only once we inline the body of A$sp$Char do we realise that we need +// C$impl$Char as well. + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + class D extends C[S] + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = + val x = new C[T]() {} // error: Inlining of inline traits looped + println("x") + +def main = + val y = new A[Char] {} diff --git a/tests/neg/specialized-trait-nested-specialized.scala b/tests/neg/specialized-trait-nested-specialized.scala new file mode 100644 index 000000000000..54d2753a9bcb --- /dev/null +++ b/tests/neg/specialized-trait-nested-specialized.scala @@ -0,0 +1,24 @@ +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] + +inline trait T2 extends T1[List[Int]] // ok +inline trait T3[S] extends T1[List[S]] // error: S should be specialized +inline trait T4[S] extends T1[List[List[S]]] // error: S should be specialized +inline trait T5[S: Specialized] extends T1[List[S]] // ok; should only specialize later +inline trait T6[T[_], S] extends T1[T[S]] // error: T should be specialized // error: S should be specialized +inline trait T7[T[_]] extends T1[T[Int]] // error: T should be specialized + +inline def foo1[S](x: T1[List[S]]): Int = 10 // error: S should be specialized +inline def foo2(x: T1[List[Int]]): Int = 10 // ok +inline def foo3[S](x: T1[List[List[S]]]): Int = 10 // error: S should be specialized +inline def foo4[S: Specialized](x: T1[List[List[S]]]): Int = 10 // ok + +inline def bar1[S] = new T1[List[S]]() {} // error: S should be specialized +inline def bar2 = new T1[List[Int]]() {} // ok +inline def bar3[S] = new T1[List[List[S]]]() {} // error: S should be specialized +inline def bar4[S: Specialized] = new T1[List[List[S]]]() {} // ok + +inline def baz1[T] = new T1[T1[T]]() {} // error: T should be Specialized +inline def baz2[T](x: T1[T1[T]]) = 1 // error: T should be Specialized +inline def baz3[T: Specialized] = new T1[T1[T]]() {} // ok +inline def baz4[T: Specialized](x: T1[T1[T]]) = 1 // ok diff --git a/tests/neg/specialized-trait-no-path-dependent.scala b/tests/neg/specialized-trait-no-path-dependent.scala new file mode 100644 index 000000000000..04c2f4f14d87 --- /dev/null +++ b/tests/neg/specialized-trait-no-path-dependent.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits +trait T1: + inline trait Foo[T: Specialized]: // error: May not define specialized traits inside traits or classes so as to make them path-dependent + def bar = "Bar" diff --git a/tests/neg/specialized-trait-no-regression-overrides-nothing.scala b/tests/neg/specialized-trait-no-regression-overrides-nothing.scala new file mode 100644 index 000000000000..3028d1fee92e --- /dev/null +++ b/tests/neg/specialized-trait-no-regression-overrides-nothing.scala @@ -0,0 +1,11 @@ +trait A1: + def foo = "A" + +trait B1: + override def foo = "B" // error: foo overrides nothing + +inline trait A2: + def foo = "A" + +inline trait B2: + override def foo = "B" // error: foo overrides nothing diff --git a/tests/neg/specialized-trait-not-inline.scala b/tests/neg/specialized-trait-not-inline.scala new file mode 100644 index 000000000000..a9c710115752 --- /dev/null +++ b/tests/neg/specialized-trait-not-inline.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.specializedTraits +trait Trait[T: Specialized] // error: Only inline traits and inline functions may take Specialized type parameters +def t[T: Specialized](a: T) = "Output" // error: Only inline traits and inline functions may take Specialized type parameters diff --git a/tests/neg/specialized-trait-opaque-type-fail.scala b/tests/neg/specialized-trait-opaque-type-fail.scala new file mode 100644 index 000000000000..68a4e67530a4 --- /dev/null +++ b/tests/neg/specialized-trait-opaque-type-fail.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized](val x: T): + opaque type Special = T + type Ordinary = T + opaque type Special2 = Int + + def foo1: Special = 10 // error: 10 does not conform to T + def bar1: Ordinary = 10 // error: 10 does not conform to T + def baz1: Special2 = 10 // This one is fine + +class B extends A[Int](100): + def foo2: Special = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + def bar2: Ordinary = 10 // This one is fine + def baz2: Special2 = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + \ No newline at end of file diff --git a/tests/neg/specialized-trait-overload.scala b/tests/neg/specialized-trait-overload.scala new file mode 100644 index 000000000000..32e973b5c6a9 --- /dev/null +++ b/tests/neg/specialized-trait-overload.scala @@ -0,0 +1,18 @@ +//> using options -language:experimental.specializedTraits + +// In principle we might be able to allow this because after erasure we will have different +// classes. The problem is we don't know the classes at the beginning of compilation +// so until erasure these overloads will be the same. It depends on to what extent the +// pre-erasure phases rely on "pre-computing" post-erasure signatures. For now we block +// it as it's not a requirement for specialized traits to work. + +inline trait Foo[T: Specialized]: + def foo(x: Foo[T]): Foo[T] + +class Bar: + def method1(x: Foo[Int]) = 10 + def method1(x: Foo[String]) = 10 // error: Conflicting definitions + +@main def Test = + val x = Bar() + diff --git a/tests/neg/specialized-trait-question-mark-in-inheritance.scala b/tests/neg/specialized-trait-question-mark-in-inheritance.scala new file mode 100644 index 000000000000..a72f21e5c7c9 --- /dev/null +++ b/tests/neg/specialized-trait-question-mark-in-inheritance.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] +inline trait T2[T: Specialized] extends T1[?] // error: Wildcard types may not be substituted for Specialized type parameters. + +inline trait T3[T: Specialized, E, F: Numeric] +inline trait T4 extends T3[?, ?, ?] // error: Wildcard types may not be substituted for Specialized type parameters. diff --git a/tests/neg/specialized-trait-question-mark-interface.scala b/tests/neg/specialized-trait-question-mark-interface.scala new file mode 100644 index 000000000000..4df161f111e7 --- /dev/null +++ b/tests/neg/specialized-trait-question-mark-interface.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[S: Specialized] + +def foo(x: Trait[?], y: Trait[? <: Long]) = // error: Wildcard types may not be substituted for Specialized type parameters. // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def bar(x: Trait[_]) = // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def baz(x: Trait[? >: Long]) = // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def foo2(x: Trait[? >: Long <: Long]) = // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def main = + foo(new Trait[Int]() {}, new Trait[Long]() {}) diff --git a/tests/neg/specialized-trait-question-mark.scala b/tests/neg/specialized-trait-question-mark.scala new file mode 100644 index 000000000000..67d2594249b9 --- /dev/null +++ b/tests/neg/specialized-trait-question-mark.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[S: Specialized] + +def main = + val x = new Trait[?] {} // error: Type argument must be fully defined diff --git a/tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala b/tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala new file mode 100644 index 000000000000..b3dff60d4e4d --- /dev/null +++ b/tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala @@ -0,0 +1,18 @@ +//> using options -language:experimental.specializedTraits + +package p1: + package p2: + object O1: + class C1: + trait T1: + object O2: + inline trait Foo[T: Specialized]: // error: May not define specialized traits inside traits or classes so as to make them path-dependent + def bar = "Bar" + + class F extends Foo + + val v = O1.C1() + val w = new v.T1 {} + def foo = new w.O2.Foo[Int] {} + +@main def Test = p1.p2.foo.bar diff --git a/tests/neg/specialized-trait-sealed-cross-file/A_1.scala b/tests/neg/specialized-trait-sealed-cross-file/A_1.scala new file mode 100644 index 000000000000..567107fb9c42 --- /dev/null +++ b/tests/neg/specialized-trait-sealed-cross-file/A_1.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +sealed inline trait Foo[T: Specialized] +inline trait Bar[T: Specialized] extends Foo[T] + +def foo(x: Foo[Int]) = x match { + case y: Bar[Int] => println("ok boomer") +} diff --git a/tests/neg/specialized-trait-sealed-cross-file/B_2.scala b/tests/neg/specialized-trait-sealed-cross-file/B_2.scala new file mode 100644 index 000000000000..bfde7e71d826 --- /dev/null +++ b/tests/neg/specialized-trait-sealed-cross-file/B_2.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +object x extends Bar[Int] // Fine because it extends Bar which is not sealed + +def foo(x: Foo[Int]) = println("hello world") // Generates $sp$ trait but actually fine + +@main def main = ??? + val z = new Foo[Int]() {} // error: Extending sealed trait diff --git a/tests/neg/specialized-trait-self-type-problems.scala b/tests/neg/specialized-trait-self-type-problems.scala new file mode 100644 index 000000000000..60aa4708005f --- /dev/null +++ b/tests/neg/specialized-trait-self-type-problems.scala @@ -0,0 +1,25 @@ +//> using options -language:experimental.specializedTraits + +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T: Specialized]: + this: T1 => + +inline trait D extends A[Int] // error: self type of D does not conform to that of A +inline trait E extends D + +inline trait B[T: Specialized]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int] // error: self type of F does not conform to that of A + +inline trait C[T: Specialized]: + this: T => + +inline trait H extends C[Test]: // error self type of H does not conform to that of C + this: T3 => + +class Cl2 extends Test with H with T3 diff --git a/tests/neg/specialized-trait-specialized-incorrect-usage.scala b/tests/neg/specialized-trait-specialized-incorrect-usage.scala new file mode 100644 index 000000000000..629b8379a96f --- /dev/null +++ b/tests/neg/specialized-trait-specialized-incorrect-usage.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits +trait Illegal: + def x: Int = + new Specialized[Int] {} // error: Cannot extend sealed trait Specialized in a different source file + 10 + + class Baz extends Specialized[Int] // error: Cannot extend sealed trait Specialized in a different source file + + trait A[T] extends Specialized[T] // error: Cannot extend sealed trait Specialized in a different source file diff --git a/tests/neg/specialized-trait-specialized-incorrect-usage2.scala b/tests/neg/specialized-trait-specialized-incorrect-usage2.scala new file mode 100644 index 000000000000..7bcecede6038 --- /dev/null +++ b/tests/neg/specialized-trait-specialized-incorrect-usage2.scala @@ -0,0 +1,24 @@ +//> using options -language:experimental.specializedTraits +trait Illegal: + def foo: Specialized[Int] // error: Specialized may only be used as a context bound + + def bar(x: Specialized[Int]): Int // error: Specialized may only be used as a context bound + + class Foo(x: Specialized[Int]) // error: Specialized may only be used as a context bound + + class Bar(val x: Specialized[Int]) // error: Specialized may only be used as a context bound + + val y: Specialized[Char] // error: Specialized may only be used as a context bound + + val v = Specialized.apply[Int] // error: Specialized may only be used as a context bound + + def z = + println(Specialized.apply[Float]) // error: Specialized may only be used as a context bound + + def a = Specialized.apply // error: Specialized may only be used as a context bound + + type V = Specialized // error: Specialized may only be used as a context bound + + type W = Specialized[Int] // error: Specialized may only be used as a context bound + + val b = Specialized // error: Specialized may only be used as a context bound diff --git a/tests/neg/specialized-trait-specialized-incorrect-usage3.scala b/tests/neg/specialized-trait-specialized-incorrect-usage3.scala new file mode 100644 index 000000000000..011390a3fb70 --- /dev/null +++ b/tests/neg/specialized-trait-specialized-incorrect-usage3.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.specializedTraits +trait Illegal: + def x(v: Specialized) = 10 // error: Missing type parameter for Specialized diff --git a/tests/neg/specialized-trait-trait-extends-specialized-trait-no-param.scala b/tests/neg/specialized-trait-trait-extends-specialized-trait-no-param.scala new file mode 100644 index 000000000000..9a747d7daf79 --- /dev/null +++ b/tests/neg/specialized-trait-trait-extends-specialized-trait-no-param.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized] + +trait Bar extends Foo[Int]: // error: Specialized traits may not be extended by ordinary traits. They may only be extended by classes, objects or inline/specialized traits. + def myMethod = "Hello I am a method" diff --git a/tests/neg/specialized-trait-trait-extends-specialized-trait.scala b/tests/neg/specialized-trait-trait-extends-specialized-trait.scala new file mode 100644 index 000000000000..246da494f134 --- /dev/null +++ b/tests/neg/specialized-trait-trait-extends-specialized-trait.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized](x: T): + def foo = x + +trait Bar extends Foo[Int]: // error: Specialized traits may not be extended by ordinary traits. They may only be extended by classes, objects or inline/specialized traits. + def myMethod = "Hello I am a method" + +def f(b: Foo[Int]) = println(s"We found the following value of foo ${b.foo}") + +@main def main = + val x = new Bar with Foo(19) {} + f(x) diff --git a/tests/neg/specialized-trait-variance-not-allowed.scala b/tests/neg/specialized-trait-variance-not-allowed.scala new file mode 100644 index 000000000000..1ddc0ddcaeda --- /dev/null +++ b/tests/neg/specialized-trait-variance-not-allowed.scala @@ -0,0 +1,49 @@ +//> using options -language:experimental.specializedTraits + +trait Animal: + def makeNoise: String +class Lion extends Animal: + override def makeNoise = "ROAR!" +class Dog extends Animal: + override def makeNoise: String = "BARK!" + +trait Material +class Paper extends Material +class Newspaper extends Paper + +inline trait MyList[+T: Specialized](val xs: List[T]): + def map[S](f: T => S) = xs.map(f) +inline trait Bin[-T: Specialized]: + def throwAway(x: T) = println(s"Throwing away ${x}") + +def sound(dogs: MyList[Dog]) = + dogs.map(_.makeNoise) + +def throwAwayGeneralPaper(bin: Bin[Paper]) = + val a4 = Paper() + bin.throwAway(a4) + +def main = + /* These ones are not allowed normally */ + val myAnimals: MyList[Animal] = new MyList(List(Dog(), Dog(), Lion())) {} + sound(myAnimals) // error: MyList[Animal] can be interpreted as MyList[Dog] due to covariance + + val myNewspaperWasteBin = new Bin[Newspaper]() {} + throwAwayGeneralPaper(myNewspaperWasteBin) // error: Bin[Newspaper] cannot be interpreted as Bin[Paper] due to contravariance + + /* These ones are specifically banned for specialized traits: */ + val myObjectBin = new Bin[Object]() {} + throwAwayTheNewspaper(myObjectBin) // error: This use of contravariance is not compatible with specialized traits. + + val myAnyBin = new Bin[Any]() {} + throwAwayTheNewspaper(myAnyBin) // error: This use of contravariance is not compatible with specialized traits. + + throwAwayAnInteger(myAnyBin) // error: This use of contravariance is not compatible with specialized traits. + + val myAnyRefBin = new Bin[AnyRef]() {} + val myAnyValBin = new Bin[AnyVal]() {} + + throwAwayTheNewspaper(myAnyRefBin) // error: This use of contravariance is not compatible with specialized traits + throwAwayAnInteger(myAnyValBin) // error: This use of contravariance is not compatible with specialized traits + + diff --git a/tests/neg/specialized-trait-variance-nothing.scala b/tests/neg/specialized-trait-variance-nothing.scala new file mode 100644 index 000000000000..a299828d04cf --- /dev/null +++ b/tests/neg/specialized-trait-variance-nothing.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +inline trait Box[+T: Specialized] + +def checkNothingInt(x: Box[Int]) = println("Good morning") +def checkNothingString(x: Box[String]) = println("Good afternoon") + +@main def Test = + val x = new Box[Nothing] {} + + checkNothingInt(x) // error: Illegal variance in specialized traits + checkNothingString(x) // error: Illegal variance in specialized traits diff --git a/tests/neg/specialized-trait-variant-warns.scala b/tests/neg/specialized-trait-variant-warns.scala new file mode 100644 index 000000000000..b4aa67f047f8 --- /dev/null +++ b/tests/neg/specialized-trait-variant-warns.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits -Werror + +// nopos-error: no warnings under Werror + +inline trait Bin[-T: Specialized] // warn +inline trait List[+T: Specialized] // warn diff --git a/tests/neg/trait-extends-inline-trait-indirectly.scala b/tests/neg/trait-extends-inline-trait-indirectly.scala new file mode 100644 index 000000000000..70977dd3105b --- /dev/null +++ b/tests/neg/trait-extends-inline-trait-indirectly.scala @@ -0,0 +1,8 @@ +inline trait A[T](x: T): + val y = x +inline trait B extends A[Int] +class C extends B // error: parameterized trait A is indirectly implemented, needs to be implemented directly so that arguments can be passed + +object Test: + def main(args: Array[String]): Unit = + val z = new C diff --git a/tests/neg/trait-extends-inline-trait-with-params-uninitialized.scala b/tests/neg/trait-extends-inline-trait-with-params-uninitialized.scala new file mode 100644 index 000000000000..37646bc9b7da --- /dev/null +++ b/tests/neg/trait-extends-inline-trait-with-params-uninitialized.scala @@ -0,0 +1,4 @@ +inline trait A[T](x: T): + val y = x +trait B extends A[Int] // error: only parameterless inline traits may be extended by ordinary traits. +class C extends B, A[Int](4) diff --git a/tests/neg/trait-inline-trait-clashing.scala b/tests/neg/trait-inline-trait-clashing.scala new file mode 100644 index 000000000000..3d862d975805 --- /dev/null +++ b/tests/neg/trait-inline-trait-clashing.scala @@ -0,0 +1,8 @@ +trait Foo: + def foo = 10 + +inline trait Bar: + def foo = 10 + +class C extends Bar, Foo // error: C inherits conflicting members +class D extends Foo, Bar // error: D inherits conflicting members diff --git a/tests/pos/inline-trait-1-simple-trait.scala b/tests/pos/inline-trait-1-simple-trait.scala new file mode 100644 index 000000000000..5a58b7ea2d4c --- /dev/null +++ b/tests/pos/inline-trait-1-simple-trait.scala @@ -0,0 +1,36 @@ +inline trait A: + type X = String + + val x: Int = 3 + val y: Int = x + z + private val z: Int = 1 + + def f: Int = g + def f(x: Int): Int = x + def f[T](x: T): Int = 2 + + private def g = 1 + protected[this] def p = 123 + private[this] def pp = 123456 + + def xx: X = "foo".asInstanceOf[X] +end A + +class B extends A: + /* + override type X = String + + override val x: Int = 3 + override val y: Int = this.x.+(this.z) + + private[this] val z: Int = 1 + + override def f: Int = this.g + override def f(x: Int): Int = x + override def f[T](x: T): Int = 2 + + private[this] def g: Int = 1 + + override def xx: X = "foo".asInstanceOf[X] + */ +end B diff --git a/tests/pos/inline-trait-2-generic-trait.scala b/tests/pos/inline-trait-2-generic-trait.scala new file mode 100644 index 000000000000..0df576c9c124 --- /dev/null +++ b/tests/pos/inline-trait-2-generic-trait.scala @@ -0,0 +1,13 @@ +inline trait A[T]: + def f: T = f + def f(x: T): T = x + def f[U <: T](x: U, y: T): T = x +end A + +class B extends A[Int]: + /* + override def f: Int = this.f + override def f(x: Int): Int = x + override def f[U <: Int](x: U, y: Int): Int = x + */ +end B diff --git a/tests/pos/inline-trait-3-trait-params.scala b/tests/pos/inline-trait-3-trait-params.scala new file mode 100644 index 000000000000..e2119bf9757b --- /dev/null +++ b/tests/pos/inline-trait-3-trait-params.scala @@ -0,0 +1,12 @@ +inline trait A(a: Int): + def f: Int = a + def g(b: Int): Int = a + b +end A + +class B extends A(4): + /* + private val a: Int = 4 + override def f: Int = this.a + override def g(x: Int): Int = this.a.+(b) + */ +end B diff --git a/tests/pos/inline-trait-3-trait-with-params.scala b/tests/pos/inline-trait-3-trait-with-params.scala new file mode 100644 index 000000000000..a497ebbcb7c1 --- /dev/null +++ b/tests/pos/inline-trait-3-trait-with-params.scala @@ -0,0 +1,13 @@ +inline trait A[T](a: T): + def f: T = a + def f(x: T): T = x + def f[U <: T](x: U, y: T): T = x +end A + +class B extends A[Int](3): + /* + override def f: Int = ??? + override def f(x: Int): Int = ??? + override def f[U <: Int](x: U, y: Int): Int = ??? + */ +end B diff --git a/tests/pos/inline-trait-4-no-inner-class.scala b/tests/pos/inline-trait-4-no-inner-class.scala new file mode 100644 index 000000000000..063f8e654fe5 --- /dev/null +++ b/tests/pos/inline-trait-4-no-inner-class.scala @@ -0,0 +1,24 @@ +inline trait Option[+T]: + def get: T + def isEmpty: Boolean +end Option + +inline trait Some[+T](x: T) extends Option[T]: + def get: T = x + def isEmpty: Boolean = false +end Some + +inline trait None extends Option[Nothing]: + def get: Nothing = throw new NoSuchElementException("None.get") + def isEmpty: Boolean = true +end None + +sealed trait IntOption extends Option[Int] +class IntSome(i: Int) extends IntOption, Some[Int](i) +object IntNone extends IntOption, None + +val o1: IntOption = IntSome(1) // specialized +val o2: IntOption = IntNone +val o3: Some[Int] = IntSome(1) // non-specialized +val x1: Int = o1.get // no unboxing +val x3: Int = o3.get // unboxing diff --git a/tests/pos/inline-trait-accesses-parent-val-param.scala b/tests/pos/inline-trait-accesses-parent-val-param.scala new file mode 100644 index 000000000000..9e337435e728 --- /dev/null +++ b/tests/pos/inline-trait-accesses-parent-val-param.scala @@ -0,0 +1,9 @@ +//> using options -Werror -Wsafe-init + +inline trait A[T](val x: T): + val v = x + +inline trait B extends A[Int]: + val z = x + +class C extends B, A[Int](10) diff --git a/tests/pos/inline-trait-body-abstract-def.scala b/tests/pos/inline-trait-body-abstract-def.scala new file mode 100644 index 000000000000..5b675a845236 --- /dev/null +++ b/tests/pos/inline-trait-body-abstract-def.scala @@ -0,0 +1,6 @@ +inline trait A[T]: + def x: T + +class B extends A[Int]: + def x = 1 + def f: Int = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-context-bound.scala b/tests/pos/inline-trait-body-def-context-bound.scala new file mode 100644 index 000000000000..c932500b62c5 --- /dev/null +++ b/tests/pos/inline-trait-body-def-context-bound.scala @@ -0,0 +1,6 @@ +inline trait A: + given List[String] = "AAA" :: Nil + def foo[T: List](x: T): T = summon[List[T]].headOption.getOrElse(x) + +class B extends A: + def f = foo("BBB") \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-curried-params.scala b/tests/pos/inline-trait-body-def-curried-params.scala new file mode 100644 index 000000000000..d253aa035331 --- /dev/null +++ b/tests/pos/inline-trait-body-def-curried-params.scala @@ -0,0 +1,8 @@ +inline trait A: + def x(foo: Int)(bar: Int) = + foo + bar + + def y(foo: Int) = x(foo)(foo) + +class B extends A: + def f = x(1)(2) diff --git a/tests/pos/inline-trait-body-def-extension-method.scala b/tests/pos/inline-trait-body-def-extension-method.scala new file mode 100644 index 000000000000..77b39dc502aa --- /dev/null +++ b/tests/pos/inline-trait-body-def-extension-method.scala @@ -0,0 +1,6 @@ +inline trait A: + extension [T](x: T) + def foo[U](y: U)(z: T): (T, U, T) = (x, y, z) + +class B extends A: + def f = 1.foo("2")(3) \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-final.scala b/tests/pos/inline-trait-body-def-final.scala new file mode 100644 index 000000000000..18bdeed55a1f --- /dev/null +++ b/tests/pos/inline-trait-body-def-final.scala @@ -0,0 +1,4 @@ +inline trait A: + final def f(x: Int) = x + +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-generic-singleton.scala b/tests/pos/inline-trait-body-def-generic-singleton.scala new file mode 100644 index 000000000000..2862ff3b1e93 --- /dev/null +++ b/tests/pos/inline-trait-body-def-generic-singleton.scala @@ -0,0 +1,7 @@ +inline trait A: + def foo: 3 = 3 + def bar[T](x: T): T = x + +class B extends A: + def f: "A" = bar("A") + def g: 3 = foo \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-generic.scala b/tests/pos/inline-trait-body-def-generic.scala new file mode 100644 index 000000000000..75d932f2f04a --- /dev/null +++ b/tests/pos/inline-trait-body-def-generic.scala @@ -0,0 +1,6 @@ +inline trait A: + def foo[T] = 1 + def bar: [U <: A] => U => Int = [U <: A] => (x: U) => x.foo + +class B extends A: + def f = foo[String] + bar(this) \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-implicit.scala b/tests/pos/inline-trait-body-def-implicit.scala new file mode 100644 index 000000000000..51381b394cef --- /dev/null +++ b/tests/pos/inline-trait-body-def-implicit.scala @@ -0,0 +1,6 @@ +inline trait A: + implicit val x: String = "AAA" + def foo(implicit s: String): String = s + s + +class B extends A: + def f = foo \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline-abstract.scala b/tests/pos/inline-trait-body-def-inline-abstract.scala new file mode 100644 index 000000000000..c385c397a3f0 --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline-abstract.scala @@ -0,0 +1,6 @@ +inline trait A: + inline def x: Int + +class B extends A: + inline def x = 1 + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline-compiletime.scala b/tests/pos/inline-trait-body-def-inline-compiletime.scala new file mode 100644 index 000000000000..975bffc76ac2 --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline-compiletime.scala @@ -0,0 +1,9 @@ +import scala.compiletime.* + +inline trait A: + inline def f[T <: String] = + inline if constValue[T] == "I consent" then "All is OK!" + else error("You must consent!") + +class B extends A: + val x = f["I consent"] \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline-transparent.scala b/tests/pos/inline-trait-body-def-inline-transparent.scala new file mode 100644 index 000000000000..7e48cdec1e0f --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline-transparent.scala @@ -0,0 +1,5 @@ +inline trait A: + transparent inline def x = 1 + +class B extends A: + def f: 1 = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline.scala b/tests/pos/inline-trait-body-def-inline.scala new file mode 100644 index 000000000000..8b3751398365 --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline.scala @@ -0,0 +1,5 @@ +inline trait A: + inline def x = 1 + +class B extends A: + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-lambda.scala b/tests/pos/inline-trait-body-def-lambda.scala new file mode 100644 index 000000000000..4eec58d40749 --- /dev/null +++ b/tests/pos/inline-trait-body-def-lambda.scala @@ -0,0 +1,5 @@ +inline trait A: + def f = (i: Int) => i + +class B extends A: + def g = f \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-local-val.scala b/tests/pos/inline-trait-body-def-local-val.scala new file mode 100644 index 000000000000..c503432ec175 --- /dev/null +++ b/tests/pos/inline-trait-body-def-local-val.scala @@ -0,0 +1,7 @@ +inline trait A: + def f = + val foo = 1 + foo + +class B extends A: + def g = f \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-params.scala b/tests/pos/inline-trait-body-def-params.scala new file mode 100644 index 000000000000..79018db761a3 --- /dev/null +++ b/tests/pos/inline-trait-body-def-params.scala @@ -0,0 +1,7 @@ +inline trait A: + def x(foo: Int, bar: Unit) = + bar + foo + +class B extends A: + def f = x(1, ()) \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-parens.scala b/tests/pos/inline-trait-body-def-parens.scala new file mode 100644 index 000000000000..17c80e9d8ab7 --- /dev/null +++ b/tests/pos/inline-trait-body-def-parens.scala @@ -0,0 +1,5 @@ +inline trait A: + def x() = 1 + +class B extends A: + def f = x() \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-simple.scala b/tests/pos/inline-trait-body-def-simple.scala new file mode 100644 index 000000000000..edffe37168be --- /dev/null +++ b/tests/pos/inline-trait-body-def-simple.scala @@ -0,0 +1,5 @@ +inline trait A: + def x = 1 + +class B extends A: + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-using.scala b/tests/pos/inline-trait-body-def-using.scala new file mode 100644 index 000000000000..1436962f6b48 --- /dev/null +++ b/tests/pos/inline-trait-body-def-using.scala @@ -0,0 +1,6 @@ +inline trait A: + given String = "AAA" + def foo(using s: String): String = s + s + +class B extends A: + def f = foo \ No newline at end of file diff --git a/tests/pos/inline-trait-body-lazy-val.scala b/tests/pos/inline-trait-body-lazy-val.scala new file mode 100644 index 000000000000..37ab5c249375 --- /dev/null +++ b/tests/pos/inline-trait-body-lazy-val.scala @@ -0,0 +1,5 @@ +inline trait A: + lazy val x = 1 + +class B extends A: + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-macro-suspend/Macro.scala b/tests/pos/inline-trait-body-macro-suspend/Macro.scala new file mode 100644 index 000000000000..8aa252d8b7d0 --- /dev/null +++ b/tests/pos/inline-trait-body-macro-suspend/Macro.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def foo(): Int = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Int] = + '{3} + +inline trait A: + val i: Int = foo() diff --git a/tests/pos/inline-trait-body-macro-suspend/Test.scala b/tests/pos/inline-trait-body-macro-suspend/Test.scala new file mode 100644 index 000000000000..5079eecd497d --- /dev/null +++ b/tests/pos/inline-trait-body-macro-suspend/Test.scala @@ -0,0 +1,2 @@ +class B extends A: + def test = foo() \ No newline at end of file diff --git a/tests/pos/inline-trait-body-macro/Macro_1.scala b/tests/pos/inline-trait-body-macro/Macro_1.scala new file mode 100644 index 000000000000..8aa252d8b7d0 --- /dev/null +++ b/tests/pos/inline-trait-body-macro/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def foo(): Int = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Int] = + '{3} + +inline trait A: + val i: Int = foo() diff --git a/tests/pos/inline-trait-body-macro/Test_2.scala b/tests/pos/inline-trait-body-macro/Test_2.scala new file mode 100644 index 000000000000..26e47fd25fdb --- /dev/null +++ b/tests/pos/inline-trait-body-macro/Test_2.scala @@ -0,0 +1 @@ +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-body-rhs-type.scala b/tests/pos/inline-trait-body-rhs-type.scala new file mode 100644 index 000000000000..5b439ddf0c19 --- /dev/null +++ b/tests/pos/inline-trait-body-rhs-type.scala @@ -0,0 +1,6 @@ +inline trait A[T](x: T): + def f: T = x: T + def g: T = identity[T](x) + def h: this.type = this: this.type + +class B extends A("Hello") diff --git a/tests/pos/inline-trait-body-setter.scala b/tests/pos/inline-trait-body-setter.scala new file mode 100644 index 000000000000..f1fcf528da9f --- /dev/null +++ b/tests/pos/inline-trait-body-setter.scala @@ -0,0 +1,6 @@ +inline trait A: + def x = 1 + def x_= (x: Int) = ??? + var y = 1 + +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-body-type.scala b/tests/pos/inline-trait-body-type.scala new file mode 100644 index 000000000000..35cad5c81c99 --- /dev/null +++ b/tests/pos/inline-trait-body-type.scala @@ -0,0 +1,5 @@ +inline trait A[T]: + type U = String + +class B extends A[Int]: + def f: U = "ABD" \ No newline at end of file diff --git a/tests/pos/inline-trait-body-val-inline.scala b/tests/pos/inline-trait-body-val-inline.scala new file mode 100644 index 000000000000..37f76fbd915c --- /dev/null +++ b/tests/pos/inline-trait-body-val-inline.scala @@ -0,0 +1,5 @@ +inline trait A: + inline val x = 1 + +class B extends A: + def f = x diff --git a/tests/pos/inline-trait-body-val.scala b/tests/pos/inline-trait-body-val.scala new file mode 100644 index 000000000000..33552386cf79 --- /dev/null +++ b/tests/pos/inline-trait-body-val.scala @@ -0,0 +1,5 @@ +inline trait A: + val x = 1 + +class B extends A: + def f = x diff --git a/tests/pos/inline-trait-clash-type-type.scala b/tests/pos/inline-trait-clash-type-type.scala new file mode 100644 index 000000000000..4e71330d4a69 --- /dev/null +++ b/tests/pos/inline-trait-clash-type-type.scala @@ -0,0 +1,8 @@ +trait One +trait Two extends One + +inline trait A: + type T <: One + +class C extends A: + type T = Two diff --git a/tests/pos/inline-trait-class-type-generic.scala b/tests/pos/inline-trait-class-type-generic.scala new file mode 100644 index 000000000000..572e88c34ae1 --- /dev/null +++ b/tests/pos/inline-trait-class-type-generic.scala @@ -0,0 +1,9 @@ +object MyObject + +inline trait A[T](x: T): + def foo: T = x + +class B extends A[MyObject.type](MyObject): + val y = 1 + +def h(x: B) = x.foo diff --git a/tests/pos/inline-trait-extends-abstract-class.scala b/tests/pos/inline-trait-extends-abstract-class.scala new file mode 100644 index 000000000000..ce1b2c1a10af --- /dev/null +++ b/tests/pos/inline-trait-extends-abstract-class.scala @@ -0,0 +1,8 @@ +abstract class A: + def foo: Int + +inline trait B(x: Int) extends A: + val y = x + override def foo = 10 + +class C extends B(15) diff --git a/tests/pos/inline-trait-extends-class.scala b/tests/pos/inline-trait-extends-class.scala new file mode 100644 index 000000000000..46cfce7da628 --- /dev/null +++ b/tests/pos/inline-trait-extends-class.scala @@ -0,0 +1,8 @@ +class A: + def foo: Int = 12 + +inline trait B(x: Int) extends A: + val y = x + override def foo = 10 + +class C extends B(15) diff --git a/tests/pos/inline-trait-extends-non-inline.scala b/tests/pos/inline-trait-extends-non-inline.scala new file mode 100644 index 000000000000..f66746b7dfeb --- /dev/null +++ b/tests/pos/inline-trait-extends-non-inline.scala @@ -0,0 +1,6 @@ +trait A: + def foo = "Hello World" + +inline trait B extends A + +trait C extends B diff --git a/tests/pos/inline-trait-extends-trait-with-parameters.scala b/tests/pos/inline-trait-extends-trait-with-parameters.scala new file mode 100644 index 000000000000..63ea6b9f0b3b --- /dev/null +++ b/tests/pos/inline-trait-extends-trait-with-parameters.scala @@ -0,0 +1,8 @@ +trait A(val z: Int): + def foo: Int + +inline trait B(x: Int) extends A: + val y = x + z + override def foo = 10 + +class C extends B(15), A(10) diff --git a/tests/pos/inline-trait-extends-trait.scala b/tests/pos/inline-trait-extends-trait.scala new file mode 100644 index 000000000000..0977d7faa67a --- /dev/null +++ b/tests/pos/inline-trait-extends-trait.scala @@ -0,0 +1,8 @@ +trait A: + def foo: Int + +inline trait B(x: Int) extends A: + val y = x + override def foo = 10 + +class C extends B(15) diff --git a/tests/pos/inline-trait-inheritance-multiple-override.scala b/tests/pos/inline-trait-inheritance-multiple-override.scala new file mode 100644 index 000000000000..8a429deb5101 --- /dev/null +++ b/tests/pos/inline-trait-inheritance-multiple-override.scala @@ -0,0 +1,20 @@ +inline trait IT1: + def i: Int = 1 + def f[T](x: T): T = x + +inline trait IT2: + def j: String = "inline" + def g(x: Int): String = x.toString() + +trait T: + def k: List[Nothing] + def h(x: Int, y: Double): Double = x + y + +class C1 extends IT1, T: + override def i: Int = 123456 + def k = Nil + override def h(x: Int, y: Double): Double = 1.0 + +class C2 extends IT1, IT2: + override def i: Int = 567890 + override def f[T](x: T): T = ??? \ No newline at end of file diff --git a/tests/pos/inline-trait-inheritance-multiple.scala b/tests/pos/inline-trait-inheritance-multiple.scala new file mode 100644 index 000000000000..15013572f9b7 --- /dev/null +++ b/tests/pos/inline-trait-inheritance-multiple.scala @@ -0,0 +1,17 @@ +object InlineTraits: + inline trait IT1: + def i: Int = 1 + def f[T](x: T): T = x + + inline trait IT2: + def j: String = "inline" + def g(x: Int): String = x.toString() + +trait T: + def k: List[Nothing] + def h(x: Int, y: Double): Double = x + y + +class C1 extends InlineTraits.IT1, T: + def k = Nil + +class C2 extends InlineTraits.IT1, InlineTraits.IT2 \ No newline at end of file diff --git a/tests/pos/inline-trait-inheritance-single-abstract-class-override.scala b/tests/pos/inline-trait-inheritance-single-abstract-class-override.scala new file mode 100644 index 000000000000..7baaf3885d6b --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-abstract-class-override.scala @@ -0,0 +1,7 @@ +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +abstract class AC extends IT: + override def i: Int = 123456 + def j: Int diff --git a/tests/pos/inline-trait-inheritance-single-abstract-class.scala b/tests/pos/inline-trait-inheritance-single-abstract-class.scala new file mode 100644 index 000000000000..49243dfd56ec --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-abstract-class.scala @@ -0,0 +1,6 @@ +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +abstract class AC extends IT: + def j: Int diff --git a/tests/pos/inline-trait-inheritance-single-object-override.scala b/tests/pos/inline-trait-inheritance-single-object-override.scala new file mode 100644 index 000000000000..7e7726e0e8bb --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-object-override.scala @@ -0,0 +1,6 @@ +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +object O extends IT: + override def i: Int = 123456 diff --git a/tests/pos/inline-trait-inheritance-single-object.scala b/tests/pos/inline-trait-inheritance-single-object.scala new file mode 100644 index 000000000000..3916f2e3dc36 --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-object.scala @@ -0,0 +1,5 @@ +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +object O extends IT diff --git a/tests/pos/inline-trait-inter-trait-inlining-no-cycle.scala b/tests/pos/inline-trait-inter-trait-inlining-no-cycle.scala new file mode 100644 index 000000000000..31da912767e7 --- /dev/null +++ b/tests/pos/inline-trait-inter-trait-inlining-no-cycle.scala @@ -0,0 +1,16 @@ +inline trait F: + def i = println("HELLO WORLD") + +inline trait E: + def h = + new F() {} + +inline trait D: + def g = + new E() {} + +inline trait C: + def f = + new D() {} + +class MyClass extends C, D, E, F diff --git a/tests/pos/inline-trait-multiple-files/A.scala b/tests/pos/inline-trait-multiple-files/A.scala new file mode 100644 index 000000000000..a137403cdf53 --- /dev/null +++ b/tests/pos/inline-trait-multiple-files/A.scala @@ -0,0 +1,2 @@ +inline trait A: + val i: Int = 1 \ No newline at end of file diff --git a/tests/pos/inline-trait-multiple-files/B.scala b/tests/pos/inline-trait-multiple-files/B.scala new file mode 100644 index 000000000000..26e47fd25fdb --- /dev/null +++ b/tests/pos/inline-trait-multiple-files/B.scala @@ -0,0 +1 @@ +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-multiple-stages-defs/A_1.scala b/tests/pos/inline-trait-multiple-stages-defs/A_1.scala new file mode 100644 index 000000000000..28781c194dd1 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-defs/A_1.scala @@ -0,0 +1,11 @@ +inline trait A(x: Int): + def f: Int = 1 + def g(a: Int): Int = 2 + def h: Int + val i: Int = 3 + val j: Int + var k: Int = 4 + + inline val a = 5 + inline val b = a + inline def b(a: Int): Int = 6 diff --git a/tests/pos/inline-trait-multiple-stages-defs/B_2.scala b/tests/pos/inline-trait-multiple-stages-defs/B_2.scala new file mode 100644 index 000000000000..b1a1d7d6af3d --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-defs/B_2.scala @@ -0,0 +1,3 @@ +class B extends A(10): + def h: Int = 11 + val j: Int = 12 diff --git a/tests/pos/inline-trait-multiple-stages-generic-defs/A_1.scala b/tests/pos/inline-trait-multiple-stages-generic-defs/A_1.scala new file mode 100644 index 000000000000..3660b1e933ce --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-generic-defs/A_1.scala @@ -0,0 +1,9 @@ +inline trait A[T](x: T): + def f: T = x + def g(a: T): T = a + def h: T + val i: T = x + val j: T + var k: T = x + + inline def method(a: T): T = x diff --git a/tests/pos/inline-trait-multiple-stages-generic-defs/B_2.scala b/tests/pos/inline-trait-multiple-stages-generic-defs/B_2.scala new file mode 100644 index 000000000000..b595626379b8 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-generic-defs/B_2.scala @@ -0,0 +1,3 @@ +class B extends A[Int](10): + def h: Int = 11 + val j: Int = 12 diff --git a/tests/pos/inline-trait-multiple-stages-inline-traits/A_1.scala b/tests/pos/inline-trait-multiple-stages-inline-traits/A_1.scala new file mode 100644 index 000000000000..2c130a7d8613 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-inline-traits/A_1.scala @@ -0,0 +1,3 @@ +inline trait A: + def foo = "hello world" + \ No newline at end of file diff --git a/tests/pos/inline-trait-multiple-stages-inline-traits/B_2.scala b/tests/pos/inline-trait-multiple-stages-inline-traits/B_2.scala new file mode 100644 index 000000000000..68b16caeff9b --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-inline-traits/B_2.scala @@ -0,0 +1,2 @@ +inline trait B extends A: + def bar = "Good morning world" diff --git a/tests/pos/inline-trait-multiple-stages-inline-traits/C_3.scala b/tests/pos/inline-trait-multiple-stages-inline-traits/C_3.scala new file mode 100644 index 000000000000..da7b94cef862 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-inline-traits/C_3.scala @@ -0,0 +1,6 @@ +class C extends B + +@main def main = + val x = C() + println(x.foo) + println(x.bar) diff --git a/tests/pos/inline-trait-multiple-stages/A_1.scala b/tests/pos/inline-trait-multiple-stages/A_1.scala new file mode 100644 index 000000000000..3596275f0498 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages/A_1.scala @@ -0,0 +1,2 @@ +inline trait A: + val i: Int = 1 diff --git a/tests/pos/inline-trait-multiple-stages/B_2.scala b/tests/pos/inline-trait-multiple-stages/B_2.scala new file mode 100644 index 000000000000..a18aec3dbe9b --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages/B_2.scala @@ -0,0 +1 @@ +class B extends A diff --git a/tests/pos/inline-trait-nested-expansion.scala b/tests/pos/inline-trait-nested-expansion.scala new file mode 100644 index 000000000000..4035f91b9867 --- /dev/null +++ b/tests/pos/inline-trait-nested-expansion.scala @@ -0,0 +1,14 @@ +inline trait I1: + def foo = "foo" + +inline trait I2: + def bar = "bar" + +trait T1 extends I1: + trait T2 extends I2 + +def main = + val a = new T1() {} + val b = new a.T2() {} + a.foo + b.bar diff --git a/tests/pos/inline-trait-object-not-primitive.scala b/tests/pos/inline-trait-object-not-primitive.scala new file mode 100644 index 000000000000..7f155af53b62 --- /dev/null +++ b/tests/pos/inline-trait-object-not-primitive.scala @@ -0,0 +1,9 @@ +object Hop + +inline trait A[T](x: T): + def foo: T = x + +class B extends A[Hop.type](Hop): + val y = 1 + +def h(x: B) = x.foo diff --git a/tests/pos/inline-trait-opaque-type-multi-file/A_1.scala b/tests/pos/inline-trait-opaque-type-multi-file/A_1.scala new file mode 100644 index 000000000000..d664dd5ab298 --- /dev/null +++ b/tests/pos/inline-trait-opaque-type-multi-file/A_1.scala @@ -0,0 +1,2 @@ +inline trait A[T](val x: T): + opaque type Special = Int diff --git a/tests/pos/inline-trait-opaque-type-multi-file/B_2.scala b/tests/pos/inline-trait-opaque-type-multi-file/B_2.scala new file mode 100644 index 000000000000..3000b6115198 --- /dev/null +++ b/tests/pos/inline-trait-opaque-type-multi-file/B_2.scala @@ -0,0 +1 @@ +val x = new A[Int](5) {} diff --git a/tests/pos/inline-trait-opaque-type.scala b/tests/pos/inline-trait-opaque-type.scala new file mode 100644 index 000000000000..a922fb2033e9 --- /dev/null +++ b/tests/pos/inline-trait-opaque-type.scala @@ -0,0 +1,11 @@ +inline trait A[T](val x: T): + opaque type Special = T + + def getSpecial: Special = x + def eatSpecial(y: Special) = "Mmm, that was tasty!" + +class B extends A[Int](100) + +def foo = + val b = B() + println(b.eatSpecial(b.getSpecial)) diff --git a/tests/pos/inline-trait-override-overload.scala b/tests/pos/inline-trait-override-overload.scala new file mode 100644 index 000000000000..644de0420212 --- /dev/null +++ b/tests/pos/inline-trait-override-overload.scala @@ -0,0 +1,10 @@ +inline trait Foo[T]: + def foo(x: T) = x + def foo(x: String) = "Test" + +class Bar extends Foo[Int] + +@main def main = + val x = Bar() + println(x.foo(10)) + println(x.foo("Hello world")) \ No newline at end of file diff --git a/tests/pos/inline-trait-pair-example.scala b/tests/pos/inline-trait-pair-example.scala new file mode 100644 index 000000000000..733749aa516c --- /dev/null +++ b/tests/pos/inline-trait-pair-example.scala @@ -0,0 +1,7 @@ +package inlinetrait + +inline trait Pair[+T1, +T2](val _1: T1, val _2: T2) +class IntDoublePair(override val _1: Int, override val _2: Double) extends Pair[Int, Double](_1, _2) +class CharShortPair(override val _1: Char, override val _2: Short) extends Pair[Char, Short](_1, _2) + +def foo(i: IntDoublePair) = i._1 + i._2 diff --git a/tests/pos/inline-trait-parameter-passing.scala b/tests/pos/inline-trait-parameter-passing.scala new file mode 100644 index 000000000000..c5bba6b831b4 --- /dev/null +++ b/tests/pos/inline-trait-parameter-passing.scala @@ -0,0 +1,12 @@ +inline trait A: + def foo() = 10 + +class B extends A: + def bar() = 1000 + +def some_function(x: A) = + x.foo() + +@main def main = + val b = B() + some_function(b) diff --git a/tests/pos/inline-trait-parent-method-call.scala b/tests/pos/inline-trait-parent-method-call.scala new file mode 100644 index 000000000000..ad519a55ebfe --- /dev/null +++ b/tests/pos/inline-trait-parent-method-call.scala @@ -0,0 +1,5 @@ +inline trait A: + def generate(x: Int) = x + 1 + +class B extends A: + val y = generate(7) diff --git a/tests/pos/inline-trait-parent-ref.scala b/tests/pos/inline-trait-parent-ref.scala new file mode 100644 index 000000000000..d200f205df51 --- /dev/null +++ b/tests/pos/inline-trait-parent-ref.scala @@ -0,0 +1,7 @@ +inline trait A[T](x: T): + def foo: T = x + +class B extends A[Int](15): + val y = 1 + +def h(x: B) = x.foo diff --git a/tests/pos/inline-trait-private-evidence.scala b/tests/pos/inline-trait-private-evidence.scala new file mode 100644 index 000000000000..8cd7a5112a17 --- /dev/null +++ b/tests/pos/inline-trait-private-evidence.scala @@ -0,0 +1,13 @@ +/* + This is a special case for inline traits because the summon method is defined inline. We don't evaluate it until after + we've inlined it into child traits, but this means it can hang around in the original inline trait, which is a problem because + it throws a compiler error "method should have been inlined but was not". We need to make sure we prune it out, which initially + was missing for private fields (since it wasn't clear that this was necessary, but it is). +*/ + +inline trait A[T: Numeric]: + private val v = summon[Numeric[T]] + +inline trait B[T: Numeric] extends A[T] + +class C extends B[Int], A[Int] diff --git a/tests/pos/inline-trait-private-nested-inline-must-delete.scala b/tests/pos/inline-trait-private-nested-inline-must-delete.scala new file mode 100644 index 000000000000..5528543aee4d --- /dev/null +++ b/tests/pos/inline-trait-private-nested-inline-must-delete.scala @@ -0,0 +1,5 @@ +inline trait A[T: Numeric]: + private val num = summon[Numeric[T]] + private val x = 1 + +class B extends A[Float] diff --git a/tests/pos/inline-trait-return-ref.scala b/tests/pos/inline-trait-return-ref.scala new file mode 100644 index 000000000000..3b04ac346fee --- /dev/null +++ b/tests/pos/inline-trait-return-ref.scala @@ -0,0 +1,5 @@ +inline trait A[T](val x: T): + def foo: T = x + +class B extends A[Int](1): + def bar: Int = foo diff --git a/tests/pos/inline-trait-self-inline-two-cycle-no-use.scala b/tests/pos/inline-trait-self-inline-two-cycle-no-use.scala new file mode 100644 index 000000000000..6185497a39de --- /dev/null +++ b/tests/pos/inline-trait-self-inline-two-cycle-no-use.scala @@ -0,0 +1,15 @@ +// We throw the error only when someone tries to use the traits, because +// the theoretical infinite inlining is only an intermediate state before pruning anyway. +// At the end these traits can be pruned. + +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") diff --git a/tests/pos/inline-trait-self-type.scala b/tests/pos/inline-trait-self-type.scala new file mode 100644 index 000000000000..bf57e7b3d3e1 --- /dev/null +++ b/tests/pos/inline-trait-self-type.scala @@ -0,0 +1,26 @@ +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T]: + this: T1 => + +inline trait D extends A[Int]: + this: T1 => +inline trait E extends D: + this: T1 => + +inline trait B[T]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int]: + this: T2 & T1 => + +inline trait C[T]: + this: T => + +inline trait H extends C[Test]: + this: T3 & Test => + +class Cl2 extends Test with H with T3 diff --git a/tests/pos/inline-trait-signature-generic-context-bound.scala b/tests/pos/inline-trait-signature-generic-context-bound.scala new file mode 100644 index 000000000000..f3587911f168 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-context-bound.scala @@ -0,0 +1,4 @@ +inline trait A[T: List] + +given List[Int] = Nil +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-generic-inferred-type.scala b/tests/pos/inline-trait-signature-generic-inferred-type.scala new file mode 100644 index 000000000000..275ed0c7ec5b --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-inferred-type.scala @@ -0,0 +1,5 @@ +inline trait A[T](val x: T): + def f: T = x + +class B extends A(1): + val y: Int = f diff --git a/tests/pos/inline-trait-signature-generic-invariant.scala b/tests/pos/inline-trait-signature-generic-invariant.scala new file mode 100644 index 000000000000..cc20461e716e --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-invariant.scala @@ -0,0 +1,4 @@ +inline trait A[T]: + def f(x: T): T = x + +class B extends A[Int] diff --git a/tests/pos/inline-trait-signature-generic-parameter.scala b/tests/pos/inline-trait-signature-generic-parameter.scala new file mode 100644 index 000000000000..174ceba06a8c --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-parameter.scala @@ -0,0 +1,5 @@ +inline trait A[T](val x: T): + def f: T = x + +class B extends A[Int](1): + val y: Int = f diff --git a/tests/pos/inline-trait-signature-generic-refinement-type.scala b/tests/pos/inline-trait-signature-generic-refinement-type.scala new file mode 100644 index 000000000000..3683c55a4d02 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-refinement-type.scala @@ -0,0 +1,9 @@ +import reflect.Selectable.reflectiveSelectable + +class C[T](x: T): + def foo(): T = x + +inline trait A[T, U[T]](u: U[T]{ def foo(): T }): + def f: T = u.foo() + +class B extends A(C(1)) diff --git a/tests/pos/inline-trait-signature-generic-singleton.scala b/tests/pos/inline-trait-signature-generic-singleton.scala new file mode 100644 index 000000000000..c5d0cf2fcfb1 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-singleton.scala @@ -0,0 +1,4 @@ +inline trait A[T](x: T): + def f: T = x + +class B extends A[1](1) diff --git a/tests/pos/inline-trait-signature-generic-type-bounds.scala b/tests/pos/inline-trait-signature-generic-type-bounds.scala new file mode 100644 index 000000000000..9cf4a00c80d7 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-type-bounds.scala @@ -0,0 +1,4 @@ +inline trait A[T >: Int <: AnyVal]: + def f(x: T): T = x + +class B extends A[Int] diff --git a/tests/pos/inline-trait-signature-generic-variant.scala b/tests/pos/inline-trait-signature-generic-variant.scala new file mode 100644 index 000000000000..201a9254c47a --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-variant.scala @@ -0,0 +1,8 @@ +inline trait Cov[+T]: + def f: T = ??? + +inline trait Contr[-T]: + def f(x: T) = ??? + +class A extends Cov[AnyVal] +class B extends Contr[Int] \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-currying.scala b/tests/pos/inline-trait-signature-parameters-currying.scala new file mode 100644 index 000000000000..9d98e0596408 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-currying.scala @@ -0,0 +1,4 @@ +inline trait A(i: Int)(j: Double): + def f: Double = i + j + +class B extends A(1)(1.0) diff --git a/tests/pos/inline-trait-signature-parameters-default-value.scala b/tests/pos/inline-trait-signature-parameters-default-value.scala new file mode 100644 index 000000000000..f76056ba70df --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-default-value.scala @@ -0,0 +1,4 @@ +inline trait A(val x: Int = 4) + +class B extends A(1) +class C extends A() \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-implicit.scala b/tests/pos/inline-trait-signature-parameters-implicit.scala new file mode 100644 index 000000000000..df091c0cc7e6 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-implicit.scala @@ -0,0 +1,4 @@ +inline trait A(implicit val imp: Int) + +implicit val x: Int = 1 +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-using-nameless.scala b/tests/pos/inline-trait-signature-parameters-using-nameless.scala new file mode 100644 index 000000000000..1882b0348488 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-using-nameless.scala @@ -0,0 +1,4 @@ +inline trait A(using Int) // error + +given x: Int = 1 +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-using.scala b/tests/pos/inline-trait-signature-parameters-using.scala new file mode 100644 index 000000000000..5438359370f8 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-using.scala @@ -0,0 +1,4 @@ +inline trait A(using usng: Int) + +given x: Int = 1 +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-val-private.scala b/tests/pos/inline-trait-signature-parameters-val-private.scala new file mode 100644 index 000000000000..9306e0152f05 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-val-private.scala @@ -0,0 +1,3 @@ +inline trait A(x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-val-protected.scala b/tests/pos/inline-trait-signature-parameters-val-protected.scala new file mode 100644 index 000000000000..4127d45e9002 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-val-protected.scala @@ -0,0 +1,3 @@ +inline trait A(protected val x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-val.scala b/tests/pos/inline-trait-signature-parameters-val.scala new file mode 100644 index 000000000000..8372baa0b810 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-val.scala @@ -0,0 +1,3 @@ +inline trait A(val x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-var.scala b/tests/pos/inline-trait-signature-parameters-var.scala new file mode 100644 index 000000000000..590d273f81b6 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-var.scala @@ -0,0 +1,3 @@ +inline trait A(var x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parentheses.scala b/tests/pos/inline-trait-signature-parentheses.scala new file mode 100644 index 000000000000..8e9c674edad6 --- /dev/null +++ b/tests/pos/inline-trait-signature-parentheses.scala @@ -0,0 +1,4 @@ +inline trait A(): + def i: Int = 1 + +class B extends A diff --git a/tests/pos/inline-trait-signature-sealed.scala b/tests/pos/inline-trait-signature-sealed.scala new file mode 100644 index 000000000000..db9d6a260ad3 --- /dev/null +++ b/tests/pos/inline-trait-signature-sealed.scala @@ -0,0 +1,4 @@ +inline sealed trait A: + final def f(x: Int) = x + +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-simple.scala b/tests/pos/inline-trait-signature-simple.scala new file mode 100644 index 000000000000..6734a69a488c --- /dev/null +++ b/tests/pos/inline-trait-signature-simple.scala @@ -0,0 +1,4 @@ +inline trait A: + def i: Int = 1 + +class B extends A diff --git a/tests/pos/inline-trait-super-call-non-overridden.scala b/tests/pos/inline-trait-super-call-non-overridden.scala new file mode 100644 index 000000000000..ffd37cca62f0 --- /dev/null +++ b/tests/pos/inline-trait-super-call-non-overridden.scala @@ -0,0 +1,5 @@ +inline trait A: + def foo = 10 + +inline trait B extends A: + def bar = super.foo diff --git a/tests/pos/inline-trait-usage-anonymous-class.scala b/tests/pos/inline-trait-usage-anonymous-class.scala new file mode 100644 index 000000000000..c51dd05a6907 --- /dev/null +++ b/tests/pos/inline-trait-usage-anonymous-class.scala @@ -0,0 +1,4 @@ +inline trait A[T]: + val x: T = ??? + +val a = new A[Int] {} \ No newline at end of file diff --git a/tests/pos/inline-trait-usage-extension.scala b/tests/pos/inline-trait-usage-extension.scala new file mode 100644 index 000000000000..f9f5d87d2ceb --- /dev/null +++ b/tests/pos/inline-trait-usage-extension.scala @@ -0,0 +1,3 @@ +inline trait A + +class B extends A diff --git a/tests/pos/inline-trait-usage-inner.scala b/tests/pos/inline-trait-usage-inner.scala new file mode 100644 index 000000000000..3ed7869f0016 --- /dev/null +++ b/tests/pos/inline-trait-usage-inner.scala @@ -0,0 +1,5 @@ +object O: + inline trait A[T]: + def t: T = ??? + +class B extends O.A[Int] diff --git a/tests/pos/inline-trait-usage-param-type.scala b/tests/pos/inline-trait-usage-param-type.scala new file mode 100644 index 000000000000..e04f885f1e65 --- /dev/null +++ b/tests/pos/inline-trait-usage-param-type.scala @@ -0,0 +1,3 @@ +inline trait A + +def f(a: A) = ??? diff --git a/tests/pos/inline-trait-usage-return-type.scala b/tests/pos/inline-trait-usage-return-type.scala new file mode 100644 index 000000000000..e8015b065552 --- /dev/null +++ b/tests/pos/inline-trait-usage-return-type.scala @@ -0,0 +1,3 @@ +inline trait A + +def f: A = ??? diff --git a/tests/pos/inline-trait-usage-type-bound.scala b/tests/pos/inline-trait-usage-type-bound.scala new file mode 100644 index 000000000000..52b57f6acfa5 --- /dev/null +++ b/tests/pos/inline-trait-usage-type-bound.scala @@ -0,0 +1,3 @@ +inline trait A + +type T >: A <: A \ No newline at end of file diff --git a/tests/pos/inline-trait-usage-type.scala b/tests/pos/inline-trait-usage-type.scala new file mode 100644 index 000000000000..1c1ae110c367 --- /dev/null +++ b/tests/pos/inline-trait-usage-type.scala @@ -0,0 +1,3 @@ +inline trait A + +type T = List[A] \ No newline at end of file diff --git a/tests/pos/inline-trait-val-def-inner-class-inner-class-owners-init.scala b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners-init.scala new file mode 100644 index 000000000000..3296df058706 --- /dev/null +++ b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners-init.scala @@ -0,0 +1,16 @@ +inline trait Trait[T](x: Any): + def do_something() = println("Good morning") + +trait Trait2: + def bar() = println("bar") + +inline trait A[T]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int](new Trait2() {}) {} + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/pos/inline-trait-val-def-inner-class-inner-class-owners.scala b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners.scala new file mode 100644 index 000000000000..91362304819f --- /dev/null +++ b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners.scala @@ -0,0 +1,18 @@ +inline trait Trait[T]: + def do_something() = println("Good morning") + +trait Trait2: + def bar() = println("bar") + +inline trait A[T]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int] { + val x = new Trait2() {} + } + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala b/tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala new file mode 100644 index 000000000000..f0712d6518f6 --- /dev/null +++ b/tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala @@ -0,0 +1,11 @@ +//> using options -Werror -Wsafe-init + +inline trait A(val x: Int): + val y = x // We need to be careful not to warn on this when we inline it but leave the body in the parent, because in the child the access is fine as we inline the parameter value, + // while the rhs of val y in the parent inline trait is going to be pruned out later so the illegal access won't be possible. +class C extends A(10) + +@main def Test = + val v = C() + println(v.y) + println(v.x) diff --git a/tests/pos/specialized-trait-anonymous-class-as-param.scala b/tests/pos/specialized-trait-anonymous-class-as-param.scala new file mode 100644 index 000000000000..c0f7ce1ea8bc --- /dev/null +++ b/tests/pos/specialized-trait-anonymous-class-as-param.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized](x: Any): + def do_something() = println("Good morning") + +trait Trait2 + +@main def Test = + val b = new Trait(new Trait2() {}) {} diff --git a/tests/pos/specialized-trait-body-macro/Macro_1.scala b/tests/pos/specialized-trait-body-macro/Macro_1.scala new file mode 100644 index 000000000000..9bc4f7301e70 --- /dev/null +++ b/tests/pos/specialized-trait-body-macro/Macro_1.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits +import scala.quoted.* + +inline def foo(): Int = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Int] = + '{3} + +inline trait A[T: Specialized]: + val i: Int = foo() diff --git a/tests/pos/specialized-trait-body-macro/Test_2.scala b/tests/pos/specialized-trait-body-macro/Test_2.scala new file mode 100644 index 000000000000..ba4e4b643cf8 --- /dev/null +++ b/tests/pos/specialized-trait-body-macro/Test_2.scala @@ -0,0 +1,2 @@ +//> using options -language:experimental.specializedTraits +class B extends A[Int] \ No newline at end of file diff --git a/tests/pos/specialized-trait-class-extends-specialized-trait.scala b/tests/pos/specialized-trait-class-extends-specialized-trait.scala new file mode 100644 index 000000000000..f2ba081e5ecc --- /dev/null +++ b/tests/pos/specialized-trait-class-extends-specialized-trait.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized](x: T): + def foo = x + +class Bar extends Foo(10): + def myMethod = "Hello I am a method" + +def f(b: Foo[Int]) = println(s"We found the following value of foo ${b.foo}") + +@main def main = + val x = Bar() + f(x) diff --git a/tests/pos/specialized-trait-exhaustivity-no-problem.scala b/tests/pos/specialized-trait-exhaustivity-no-problem.scala new file mode 100644 index 000000000000..6f244a31617a --- /dev/null +++ b/tests/pos/specialized-trait-exhaustivity-no-problem.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits -Werror + +sealed inline trait Foo[S: Specialized] +inline trait Bar extends Foo[Int] + +def doMatch(x: Foo[Int]) = x match { + case x: Bar => println("Hello World") +} diff --git a/tests/pos/specialized-trait-function-takes-specialized-trait.scala b/tests/pos/specialized-trait-function-takes-specialized-trait.scala new file mode 100644 index 000000000000..6cf930cf0134 --- /dev/null +++ b/tests/pos/specialized-trait-function-takes-specialized-trait.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits + +inline trait Container[T: Specialized](val elem: T) + +def apply(f: Container[Int] => Unit, v: Container[Int]) = + f(v) + +def f(e: Container[Int]) = + println(e.elem) + +@main def Test = + val v = new Container(10) {} + apply(f, v) + apply((e: Container[Int]) => println(e), v) diff --git a/tests/pos/specialized-trait-inline-anonymous-class-defn-nowarn.scala b/tests/pos/specialized-trait-inline-anonymous-class-defn-nowarn.scala new file mode 100644 index 000000000000..01ecce142333 --- /dev/null +++ b/tests/pos/specialized-trait-inline-anonymous-class-defn-nowarn.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits -Werror +inline trait T1[T: Specialized] +inline trait T2[T: Specialized, E] + +inline def foo[F: Specialized] = new T1[F]() {} // By default this warns Anonymous class will be defined at each use site, which may lead to a larger number of classfiles. This is not true for Specialized traits as we share the $impl$ class instances. + +inline def foo[F: Specialized, E] = new T2[F, E]() {} // This should also not warn even though E is not specialized diff --git a/tests/pos/specialized-trait-inline-def-specialized.scala b/tests/pos/specialized-trait-inline-def-specialized.scala new file mode 100644 index 000000000000..98332c74994e --- /dev/null +++ b/tests/pos/specialized-trait-inline-def-specialized.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](val x: T) + +inline def foo[T: Specialized](v: Vec[T]) = v.x + +@main def Test = + val v = new Vec[Int](10) {} + println(foo(v)) diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad-manual-no-usage.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad-manual-no-usage.scala new file mode 100644 index 000000000000..87948cffb8c9 --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad-manual-no-usage.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +// No usage of the spec traits so this is fine. + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + class D extends C[S] + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala new file mode 100644 index 000000000000..281c1beee93a --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +// A[Char] => A$sp$Char => C$sp$Char, but only once we inline the body of C$sp$Char do we realise that we need +// C$impl$Char as well. + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} // Actually ok because $impl$ classes are generated outside of the inline trait; class C$impl$Char will just create an instance of itself (this is allowed) + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def main = + val y = new A[Char] {} diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala new file mode 100644 index 000000000000..61c4e83cba56 --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.specializedTraits + +// This one is fine because we only ask for an $sp$ trait interface in C and not an $impl$ class + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + class D extends C[S] + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def main = + val y = new A[Char] {} diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required.scala new file mode 100644 index 000000000000..01859563a2a3 --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required.scala @@ -0,0 +1,53 @@ +//> using options -language:experimental.specializedTraits + +// A[Char] => A$sp$Char, A$impl$Char => C$sp$Char, but only once we inline the body of C$sp$Char do we realise that we need +// B$impl$Char as well. + +// TODO: However, arguably we don't actually need B$impl$Char + +inline trait B[E: Specialized] + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + val x = new B[S] {} + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def foo(x: B[Char]) = "A" + +def main = + val y = new A[Char] {} + +/* +inline trait B[E: Specialized] + +inline trait C[S: Specialized]: + def v(x: S): S + def w: Unit + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit + +inline trait A$sp$Char: + def x(y: C$sp$Char): Unit + +class A$impl$Char: + def x(y: C$sp$Char): Unit = println("x") + +inline trait B$sp$Char + +class B$impl$Char + +inline trait C$sp$Char: + def v(x: Char): Char + def w: Unit + +def foo(x: B$sp$Char) = "A" + +def main = + val y = new A$impl$Char() {} + +*/ diff --git a/tests/pos/specialized-trait-inlining-causes-specialization.scala b/tests/pos/specialized-trait-inlining-causes-specialization.scala new file mode 100644 index 000000000000..d99267f5b9fa --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-specialization.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +inline trait D[R: Specialized] + +inline trait C[S: Specialized]: + def w(y: D[S]): Unit = println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def main = + val b = new A[Char] {} diff --git a/tests/pos/specialized-trait-multiple-stages-specialized-numeric.scala b/tests/pos/specialized-trait-multiple-stages-specialized-numeric.scala new file mode 100644 index 000000000000..40211f2b8729 --- /dev/null +++ b/tests/pos/specialized-trait-multiple-stages-specialized-numeric.scala @@ -0,0 +1,22 @@ +//> using options -language:experimental.specializedTraits +// This shouldn't generate any boxing thanks to the specialized version of Numeric + +inline def foo[T: {Specialized, Numeric2}](x: T): T = + val num = summon[Numeric2[T]] + num.plus(x, num.fromInt(1)) + +inline trait A[T: {Numeric2, Specialized}]: + def bar(x: T): T = foo(x) + +class B extends A[Int]: + def baz(x: Int): Int = foo(x) + +inline trait Numeric2[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +implicit object IntIsIntegral extends Numeric2[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y diff --git a/tests/pos/specialized-trait-object-extends-specialized-trait.scala b/tests/pos/specialized-trait-object-extends-specialized-trait.scala new file mode 100644 index 000000000000..0ba1fa4a42e8 --- /dev/null +++ b/tests/pos/specialized-trait-object-extends-specialized-trait.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait MyInterface[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + +implicit object Implementation extends MyInterface[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y diff --git a/tests/pos/specialized-trait-opaque-type-multi-file/A_1.scala b/tests/pos/specialized-trait-opaque-type-multi-file/A_1.scala new file mode 100644 index 000000000000..666e469edae4 --- /dev/null +++ b/tests/pos/specialized-trait-opaque-type-multi-file/A_1.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized](val x: T): + opaque type Special = Int diff --git a/tests/pos/specialized-trait-opaque-type-multi-file/B_2.scala b/tests/pos/specialized-trait-opaque-type-multi-file/B_2.scala new file mode 100644 index 000000000000..0327acb05a14 --- /dev/null +++ b/tests/pos/specialized-trait-opaque-type-multi-file/B_2.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.specializedTraits + +val x = new A[Int](5) {} diff --git a/tests/pos/specialized-trait-opaque-type.scala b/tests/pos/specialized-trait-opaque-type.scala new file mode 100644 index 000000000000..d82ec5e1aebd --- /dev/null +++ b/tests/pos/specialized-trait-opaque-type.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T](val x: T): + opaque type Special = T + + def getSpecial: Special = x + def eatSpecial(y: Special) = "Mmm, that was tasty!" + +class B extends A[Int](100) + +def foo = + val b = B() + println(b.eatSpecial(b.getSpecial)) diff --git a/tests/pos/specialized-trait-original-diamond.scala b/tests/pos/specialized-trait-original-diamond.scala new file mode 100644 index 000000000000..525f338ffe2f --- /dev/null +++ b/tests/pos/specialized-trait-original-diamond.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo = 10 + +inline trait B extends A[Int] +inline trait C extends A[Int] + +class Bar extends B with C diff --git a/tests/pos/specialized-trait-parameter-passing-as-named.scala b/tests/pos/specialized-trait-parameter-passing-as-named.scala new file mode 100644 index 000000000000..ac9cd596069b --- /dev/null +++ b/tests/pos/specialized-trait-parameter-passing-as-named.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits +inline trait A[T: Specialized](parameter: Int): + val x = parameter + +def main = + val y = new A[Int](parameter = 1000) {} diff --git a/tests/pos/specialized-trait-partial-complete-specialization-with-return-type.scala b/tests/pos/specialized-trait-partial-complete-specialization-with-return-type.scala new file mode 100644 index 000000000000..f2d1d71c051b --- /dev/null +++ b/tests/pos/specialized-trait-partial-complete-specialization-with-return-type.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int] {} + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/pos/specialized-trait-partial-specialization-self-type.scala b/tests/pos/specialized-trait-partial-specialization-self-type.scala new file mode 100644 index 000000000000..2c7ddeb66579 --- /dev/null +++ b/tests/pos/specialized-trait-partial-specialization-self-type.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +trait T1 +trait T2 +trait T3 +trait Test1 +trait Test2 + +inline trait A[T: Specialized, S: Specialized, Z]: + this: T1 & T & S => + +inline trait B[W: Specialized] extends A[W, Test2, Int]: + this: T1 & W & Test2 => + +class Cl2 extends B[Test1] with Test1 with Test2 with T1 + diff --git a/tests/pos/specialized-trait-partial-specialization-with-argument-passing.scala b/tests/pos/specialized-trait-partial-specialization-with-argument-passing.scala new file mode 100644 index 000000000000..037943202c35 --- /dev/null +++ b/tests/pos/specialized-trait-partial-specialization-with-argument-passing.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized, S: Specialized] +inline trait Bar[T: Specialized] extends Foo[T, Int] +class Baz extends Bar[Boolean] + +inline def receiver[S: Specialized](x: Foo[S, Int]) = + println("Good Morning") + +val x = receiver(Baz()) diff --git a/tests/pos/specialized-trait-partial-specialization.scala b/tests/pos/specialized-trait-partial-specialization.scala new file mode 100644 index 000000000000..4b05eafe9db5 --- /dev/null +++ b/tests/pos/specialized-trait-partial-specialization.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +inline trait Map[K: Specialized, V: Specialized] +inline trait MapFromInt[V: Specialized] extends Map[Int, V] +inline trait MapToInt[K: Specialized] extends Map[K, Int] +inline trait MapIntInt1 extends MapFromInt[Int] +inline trait MapIntInt2 extends MapToInt[Int] +inline trait MapIntInt3 extends MapToInt[Int], MapFromInt[Int] diff --git a/tests/pos/specialized-trait-processing-order-matters.scala b/tests/pos/specialized-trait-processing-order-matters.scala new file mode 100644 index 000000000000..132652152c7e --- /dev/null +++ b/tests/pos/specialized-trait-processing-order-matters.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +// It's important to update the symbol infos for methods whose type interface changes due to specialization +// before we update any code that uses these symbols so that we type their uses' Apply nodes correctly. +// This only becomes clear when the user is in another object (and not a top-level main method) +// because otherwise the processing follows source code ordering. + +inline trait Trait[T: Specialized]: + def bar = "bar" + +def foo(v: Trait[Int]) = v + +object Test: + def main(args: Array[String]): Unit = + val a = new Trait[Int] {} + foo(a).bar diff --git a/tests/pos/specialized-trait-question-mark-non-specialized-allowed.scala b/tests/pos/specialized-trait-question-mark-non-specialized-allowed.scala new file mode 100644 index 000000000000..74aadecea4e4 --- /dev/null +++ b/tests/pos/specialized-trait-question-mark-non-specialized-allowed.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits +inline trait T3[T: Specialized, E, F: Numeric] +def foo(v: T3[Int, ?, ?]) = + println("HELLO") + +def main = + val x = new T3[Int, String, Float] {} + foo(x) diff --git a/tests/pos/specialized-trait-self-as-param.scala b/tests/pos/specialized-trait-self-as-param.scala new file mode 100644 index 000000000000..2dce27ad5c53 --- /dev/null +++ b/tests/pos/specialized-trait-self-as-param.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized](x: Trait[T] | Int): + def do_something(): Unit = + assert(Thread.currentThread.getStackTrace()(1).getClassName().contains("$impl$")) + if x.isInstanceOf[Trait[T]] then + x.asInstanceOf[Trait[T]].do_something() + +@main def Test = + val b = new Trait(new Trait(10) {}) {} + b.do_something() diff --git a/tests/pos/specialized-trait-self-type.scala b/tests/pos/specialized-trait-self-type.scala new file mode 100644 index 000000000000..4fc2105626ac --- /dev/null +++ b/tests/pos/specialized-trait-self-type.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.specializedTraits + +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T: Specialized]: + this: T1 => + +inline trait D extends A[Int]: + this: T1 => +inline trait E extends D: + this: T1 => + +inline trait B[T: Specialized]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int]: + this: T2 & T1 => + +inline trait C[T: Specialized]: + this: T => + +inline trait H extends C[Test]: + this: T3 & Test => + +class Cl2 extends Test with H with T3 diff --git a/tests/pos/specialized-trait-simplest-implicit-type-name.scala b/tests/pos/specialized-trait-simplest-implicit-type-name.scala new file mode 100644 index 000000000000..39ca98b6456f --- /dev/null +++ b/tests/pos/specialized-trait-simplest-implicit-type-name.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](val x: T) +inline trait Bar[T: Specialized] + +@main def main = + val x = new Foo(10) {} // Type name not provided explicitly; this should still be specialized to Int. + val y = new Bar() {} // This will be specialized to Nothing. diff --git a/tests/pos/specialized-trait-simplest.scala b/tests/pos/specialized-trait-simplest.scala new file mode 100644 index 000000000000..7c230c58c2bd --- /dev/null +++ b/tests/pos/specialized-trait-simplest.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized] + +@main def main = + val x = new Foo[Int] {} diff --git a/tests/pos/specialized-trait-specialized-context-bound.scala b/tests/pos/specialized-trait-specialized-context-bound.scala new file mode 100644 index 000000000000..fb2022eaee63 --- /dev/null +++ b/tests/pos/specialized-trait-specialized-context-bound.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits + +inline trait Numeric2[T: Specialized] + +inline trait A[T: {Numeric2, Specialized}]: + def bar(x: T): T = x + +given Numeric2[Int] = new Numeric2[Int] {} + +class B extends A[Int]: + def baz(x: Int): Int = x diff --git a/tests/pos/specialized-trait-supercall-non-overridden.scala b/tests/pos/specialized-trait-supercall-non-overridden.scala new file mode 100644 index 000000000000..d4f8a9c46c5c --- /dev/null +++ b/tests/pos/specialized-trait-supercall-non-overridden.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits +inline trait A[T: Specialized]: + def foo = 10 + +inline trait B extends A[Int]: + def bar = super.foo diff --git a/tests/pos/specialized-trait-val-parameter.scala b/tests/pos/specialized-trait-val-parameter.scala new file mode 100644 index 000000000000..403561ec0794 --- /dev/null +++ b/tests/pos/specialized-trait-val-parameter.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](val x: T) + +def main = + val v = new Vec[Int](10) {} diff --git a/tests/pos/specialized-trait-var-parameter.scala b/tests/pos/specialized-trait-var-parameter.scala new file mode 100644 index 000000000000..31207dd8d58e --- /dev/null +++ b/tests/pos/specialized-trait-var-parameter.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](var x: T) + +def main = + val v = new Vec[Int](10) {} diff --git a/tests/pos/specialized-trait-very-specialized-list.scala b/tests/pos/specialized-trait-very-specialized-list.scala new file mode 100644 index 000000000000..7bef9c3786a0 --- /dev/null +++ b/tests/pos/specialized-trait-very-specialized-list.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized] + +abstract class VerySpecializedList extends Seq[Foo[Int]] + +def main = + val x: VerySpecializedList = null + val y: Foo[Int] = x(1) diff --git a/tests/pos/specialized-trait-with-param.scala b/tests/pos/specialized-trait-with-param.scala new file mode 100644 index 000000000000..aa9cba4bf7c3 --- /dev/null +++ b/tests/pos/specialized-trait-with-param.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](x: T): + def foo = x + +def f(b: Foo[Int]) = 37 + b.foo + +@main def main = + val x = new Foo[Int](42) {} + f(x) diff --git a/tests/pos/trait-extends-inline-trait.scala b/tests/pos/trait-extends-inline-trait.scala new file mode 100644 index 000000000000..8aa4040aadc4 --- /dev/null +++ b/tests/pos/trait-extends-inline-trait.scala @@ -0,0 +1,3 @@ +inline trait A: + def x = 1 +trait B extends A // This is fine as long as A has no parameters. diff --git a/tests/run-macros/tasty-extractors-owners.check b/tests/run-macros/tasty-extractors-owners.check index 982f8beec691..66fc6b891a3a 100644 --- a/tests/run-macros/tasty-extractors-owners.check +++ b/tests/run-macros/tasty-extractors-owners.check @@ -2,26 +2,26 @@ foo ValDef("macro", Inferred(), None) bar -DefDef("foo", Nil, Inferred(), None) +DefDef("foo", Nil, Inferred(), Some(Block(List(DefDef("bar", Nil, Inferred(), Some(Literal(IntConstant(1)))), ValDef("bar2", Inferred(), Some(Literal(IntConstant(2))))), Typed(Ident("bar"), Inferred())))) bar2 -DefDef("foo", Nil, Inferred(), None) +DefDef("foo", Nil, Inferred(), Some(Block(List(DefDef("bar", Nil, Inferred(), Some(Literal(IntConstant(1)))), ValDef("bar2", Inferred(), Some(Literal(IntConstant(2))))), Typed(Ident("bar"), Inferred())))) foo2 ValDef("macro", Inferred(), None) baz -ValDef("foo2", Inferred(), None) +ValDef("foo2", Inferred(), Some(Block(List(DefDef("baz", Nil, Inferred(), Some(Literal(IntConstant(3)))), ValDef("baz2", Inferred(), Some(Literal(IntConstant(4))))), Typed(Ident("baz"), Inferred())))) baz2 -ValDef("foo2", Inferred(), None) +ValDef("foo2", Inferred(), Some(Block(List(DefDef("baz", Nil, Inferred(), Some(Literal(IntConstant(3)))), ValDef("baz2", Inferred(), Some(Literal(IntConstant(4))))), Typed(Ident("baz"), Inferred())))) -ClassDef("A", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Inferred()), None, List(TypeDef("B", TypeBoundsTree(Inferred(), Inferred())), DefDef("b", Nil, Inferred(), None), ValDef("b2", Inferred(), None))) +ClassDef("A", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), None, List(TypeDef("B", TypeIdent("Int")), DefDef("b", Nil, Inferred(), Some(Literal(IntConstant(5)))), ValDef("b2", Inferred(), Some(Literal(IntConstant(6)))))) b -ClassDef("A", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Inferred()), None, List(TypeDef("B", TypeBoundsTree(Inferred(), Inferred())), DefDef("b", Nil, Inferred(), None), ValDef("b2", Inferred(), None))) +ClassDef("A", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), None, List(TypeDef("B", TypeIdent("Int")), DefDef("b", Nil, Inferred(), Some(Literal(IntConstant(5)))), ValDef("b2", Inferred(), Some(Literal(IntConstant(6)))))) b2 -ClassDef("A", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Inferred()), None, List(TypeDef("B", TypeBoundsTree(Inferred(), Inferred())), DefDef("b", Nil, Inferred(), None), ValDef("b2", Inferred(), None))) +ClassDef("A", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), None, List(TypeDef("B", TypeIdent("Int")), DefDef("b", Nil, Inferred(), Some(Literal(IntConstant(5)))), ValDef("b2", Inferred(), Some(Literal(IntConstant(6)))))) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 9c53245edcf7..3b1f2725ef9d 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -103,6 +103,11 @@ val experimentalDefinitionInLibrary = Set( // New feature: Erased trait "scala.compiletime.Erased", + // New feature: Specialized traits + // Depends on erased traits + "scala.specialize.Specialized", + "scala.specialize.Specialized$", + // New API: Multiversal equality for Named Tuples "scala.NamedTuple$.namedTupleCanEqual", ) diff --git a/tests/run/i3000b.check b/tests/run/i3000b.check index 605021c9b2c0..3684e1d2dc81 100644 --- a/tests/run/i3000b.check +++ b/tests/run/i3000b.check @@ -1,2 +1,2 @@ -Foo$$anon$1 -bar.Bar$$anon$2 +Foo$$anon$2 +bar.Bar$$anon$1 diff --git a/tests/run/inline-trait-body-lazy-val.scala b/tests/run/inline-trait-body-lazy-val.scala new file mode 100644 index 000000000000..68a9c9fd8697 --- /dev/null +++ b/tests/run/inline-trait-body-lazy-val.scala @@ -0,0 +1,9 @@ +inline trait A: + lazy val x = + throw new Exception + 1 + +class B extends A + +@main def Test: Unit = + val b = B() \ No newline at end of file diff --git a/tests/run/inline-trait-body-override-def.scala b/tests/run/inline-trait-body-override-def.scala new file mode 100644 index 000000000000..2ec53e02e669 --- /dev/null +++ b/tests/run/inline-trait-body-override-def.scala @@ -0,0 +1,9 @@ +inline trait A: + def foo: Unit = throw Exception("I should not be run!") + +class B extends A: + override def foo: Unit = () + +@main def Test = + val b = B() + b.foo diff --git a/tests/run/inline-trait-body-override-val.check b/tests/run/inline-trait-body-override-val.check new file mode 100644 index 000000000000..0cfbf08886fc --- /dev/null +++ b/tests/run/inline-trait-body-override-val.check @@ -0,0 +1 @@ +2 diff --git a/tests/run/inline-trait-body-override-val.scala b/tests/run/inline-trait-body-override-val.scala new file mode 100644 index 000000000000..ffd71ca0df56 --- /dev/null +++ b/tests/run/inline-trait-body-override-val.scala @@ -0,0 +1,9 @@ +inline trait A: + val x: Int = 1 + +class B extends A: + override val x = 2 + +@main def Test = + val b = B() + println(b.x) diff --git a/tests/run/inline-trait-body-statements.check b/tests/run/inline-trait-body-statements.check new file mode 100644 index 000000000000..73b256c35fd0 --- /dev/null +++ b/tests/run/inline-trait-body-statements.check @@ -0,0 +1,8 @@ +1 +foo +foo +1 +foo +bar +bar +bar diff --git a/tests/run/inline-trait-body-statements.scala b/tests/run/inline-trait-body-statements.scala new file mode 100644 index 000000000000..5da24022e99d --- /dev/null +++ b/tests/run/inline-trait-body-statements.scala @@ -0,0 +1,22 @@ +inline trait A[T]: + var x = 1 + def foo = + if x == 1 then println("1") + x = 1 - x + println("foo") + + foo + foo + foo + +class B extends A[Int]: + def bar = + if x == 1 then println("1") + println("bar") + + bar + bar + bar + +@main def Test = + B() \ No newline at end of file diff --git a/tests/run/inline-trait-body-var.check b/tests/run/inline-trait-body-var.check new file mode 100644 index 000000000000..94ebaf900161 --- /dev/null +++ b/tests/run/inline-trait-body-var.check @@ -0,0 +1,4 @@ +1 +2 +3 +4 diff --git a/tests/run/inline-trait-body-var.scala b/tests/run/inline-trait-body-var.scala new file mode 100644 index 000000000000..fda8fc3109b7 --- /dev/null +++ b/tests/run/inline-trait-body-var.scala @@ -0,0 +1,15 @@ +inline trait A: + var x = 1 + +class B extends A: + def f = + val old = x + x += 1 + old + +@main def Test = + val b = B() + println(b.f) + println(b.f) + println(b.f) + println(b.f) diff --git a/tests/run/inline-trait-child-and-grandchild-override.scala b/tests/run/inline-trait-child-and-grandchild-override.scala new file mode 100644 index 000000000000..f2b0b72e9aa3 --- /dev/null +++ b/tests/run/inline-trait-child-and-grandchild-override.scala @@ -0,0 +1,11 @@ +inline trait GreatGrandParent: + val x = 10 +inline trait GrandParent extends GreatGrandParent: + override val x = 11 +inline trait Parent extends GrandParent: + override val x = 12 +class C extends Parent + +@main def Test = + val x = C() + assert(x.x == 12) diff --git a/tests/run/inline-trait-clash-method-method.scala b/tests/run/inline-trait-clash-method-method.scala new file mode 100644 index 000000000000..8871681d977e --- /dev/null +++ b/tests/run/inline-trait-clash-method-method.scala @@ -0,0 +1,9 @@ +inline trait A: + def x(y: String) = "Hello world" + +class C extends A: + override def x(y: String) = "Hello world2" + +@main def Test = + val v = C() + assert(v.x("Hello World") == "Hello world2") diff --git a/tests/run/inline-trait-clash-method-val-param.scala b/tests/run/inline-trait-clash-method-val-param.scala new file mode 100644 index 000000000000..7e7ebab600cb --- /dev/null +++ b/tests/run/inline-trait-clash-method-val-param.scala @@ -0,0 +1,9 @@ +inline trait A: + def x = "Hello world" + +class C extends A: + override val x = "Overridden" + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/run/inline-trait-clash-param-method.scala b/tests/run/inline-trait-clash-param-method.scala new file mode 100644 index 000000000000..23bb7565b3bd --- /dev/null +++ b/tests/run/inline-trait-clash-param-method.scala @@ -0,0 +1,8 @@ +inline trait A(x: Int) + +class C extends A(10): + def x = 1000 + +@main def Test = + val v = C() + assert(v.x == 1000) diff --git a/tests/run/inline-trait-clash-param-param.scala b/tests/run/inline-trait-clash-param-param.scala new file mode 100644 index 000000000000..4f4c0776a83b --- /dev/null +++ b/tests/run/inline-trait-clash-param-param.scala @@ -0,0 +1,10 @@ +// Param x in A will be renamed so it doesn't clash. + +inline trait A(x: Int) + +class C(x: String) extends A(10): + val y = x + +@main def Test = + val v = C("Hello World") + assert(v.y == "Hello World") diff --git a/tests/run/inline-trait-clash-param-val-param.scala b/tests/run/inline-trait-clash-param-val-param.scala new file mode 100644 index 000000000000..30807db9553e --- /dev/null +++ b/tests/run/inline-trait-clash-param-val-param.scala @@ -0,0 +1,11 @@ +// Allowed. We rename A.x, A.y to avoid a clash. + +inline trait A(x: Int, y: Int) + +class C(val x: Int) extends A(10, 100): + val y: Int = 10 + +@main def Test = + val v = C(5) + assert(v.x == 5) + assert(v.y == 10) diff --git a/tests/run/inline-trait-clash-param-var-param.scala b/tests/run/inline-trait-clash-param-var-param.scala new file mode 100644 index 000000000000..c2514fb686d0 --- /dev/null +++ b/tests/run/inline-trait-clash-param-var-param.scala @@ -0,0 +1,11 @@ +// Allowed. We rename A.x, A.y to avoid a clash. + +inline trait A(x: Int, y: Int) + +class C(var x: Int) extends A(10, 100): + val y: Int = 10 + +@main def Test = + val v = C(5) + assert(v.x == 5) + assert(v.y == 10) diff --git a/tests/run/inline-trait-clash-val-param-val-param.scala b/tests/run/inline-trait-clash-val-param-val-param.scala new file mode 100644 index 000000000000..e1f46052c9fb --- /dev/null +++ b/tests/run/inline-trait-clash-val-param-val-param.scala @@ -0,0 +1,8 @@ +inline trait A(val x: Int, val y: Int) +class C(override val y: Int) extends A(10, 54): + override val x = 1000 + +@main def Test = + val v = C(44) + assert(v.x == 1000) + assert(v.y == 44) diff --git a/tests/run/inline-trait-clashing-parent-params.scala b/tests/run/inline-trait-clashing-parent-params.scala new file mode 100644 index 000000000000..5103ff6291dc --- /dev/null +++ b/tests/run/inline-trait-clashing-parent-params.scala @@ -0,0 +1,14 @@ +// These params should be renamed (as they are private) so no clash + +inline trait A(x: Int): + val y = x + +inline trait B(x: Int): + val z = x + +class C extends A(10), B(11) + +@main def Test = + val v = C() + assert(v.y == 10) + assert(v.z == 11) diff --git a/tests/run/inline-trait-enclosing-super-call.scala b/tests/run/inline-trait-enclosing-super-call.scala new file mode 100644 index 000000000000..2016de44b827 --- /dev/null +++ b/tests/run/inline-trait-enclosing-super-call.scala @@ -0,0 +1,14 @@ +inline trait A: + def bar = "A" + +class E: + def bar = "E" + +class D extends E: + class C extends A: + def foo = D.super.bar // We shouldn't touch this super call when inlining A + +@main def Test = + val d = D() + val c = d.C() + assert(c.foo == "E") diff --git a/tests/run/inline-trait-enclosing-supercall-in-inline-trait.scala b/tests/run/inline-trait-enclosing-supercall-in-inline-trait.scala new file mode 100644 index 000000000000..ba2627b8dab0 --- /dev/null +++ b/tests/run/inline-trait-enclosing-supercall-in-inline-trait.scala @@ -0,0 +1,13 @@ +trait A: + def foo = 10 + +class B extends A: + inline trait T: + def foo = B.super.foo + + class C2 extends T + +@main def Test = + val x = new B() + val y = new x.C2() + assert(y.foo == 10) diff --git a/tests/run/inline-trait-indirect-with-params.scala b/tests/run/inline-trait-indirect-with-params.scala new file mode 100644 index 000000000000..fccd89cf9db4 --- /dev/null +++ b/tests/run/inline-trait-indirect-with-params.scala @@ -0,0 +1,11 @@ +inline trait A[T](x: T): + val y = x + def foo() = x +inline trait B extends A[Int] +class C extends A[Int](10), B + +object Test: + def main(args: Array[String]): Unit = + val z: B = new C + assert(z.foo() == 10) + assert(z.y == 10) \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-diamond-simple-trait.check b/tests/run/inline-trait-inheritance-diamond-simple-trait.check new file mode 100644 index 000000000000..adbdca39e3a4 --- /dev/null +++ b/tests/run/inline-trait-inheritance-diamond-simple-trait.check @@ -0,0 +1,4 @@ +1 +2 +999 +4 diff --git a/tests/run/inline-trait-inheritance-diamond-simple-trait.scala b/tests/run/inline-trait-inheritance-diamond-simple-trait.scala new file mode 100644 index 000000000000..0bb66f48b739 --- /dev/null +++ b/tests/run/inline-trait-inheritance-diamond-simple-trait.scala @@ -0,0 +1,22 @@ +trait TGP[T]: + def i: T + def f(x: T): T = x + +inline trait IT1[T](x: T) extends TGP[T]: + override def i: T = x + +inline trait IT2 extends TGP[Int]: + override def i: Int = 999 + def j: String = "inline" + def g(x: Int): String = x.toString() + +trait T extends TGP[Int] + +class C1 extends T, IT1[Int](1) +class C2 extends IT1[Int](2), T +class C3 extends IT1[Int](3), IT2 +class C4 extends IT2, IT1[Int](4) + +@main def Test: Unit = + for c <- List(C1(), C2(), C3(), C4()) + do println(c.i) \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-inline-ancestors/inlinetraits.scala b/tests/run/inline-trait-inheritance-inline-ancestors/inlinetraits.scala new file mode 100644 index 000000000000..e695ef3efd1a --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-ancestors/inlinetraits.scala @@ -0,0 +1,47 @@ +package inlinetraits + +val inlineValues: List[Int] = + val c = C() + List(c.zero, c.eleven, c.twelve, c.thirteen, c.twentyOne, c.twentyTwo, c.thirty) + +inline trait T0: + def zero: Int = 0 + def eleven: Int = 0 + def twelve: Int = 0 + def twentyOne: Int = 0 + def thirteen: Int = 0 + def twentyTwo: Int = 0 + def thirty: Int = 0 + +inline trait T11 extends T0: + override def eleven: Int = 11 + override def twelve: Int = 11 + override def twentyOne: Int = 11 + override def thirteen: Int = 11 + override def twentyTwo: Int = 11 + override def thirty: Int = 11 + +inline trait T12 extends T0: + override def twelve: Int = 12 + override def twentyOne: Int = 12 + override def thirteen: Int = 12 + override def twentyTwo: Int = 12 + override def thirty: Int = 12 + +inline trait T21 extends T11, T12: + override def twentyOne: Int = 21 + override def thirteen: Int = 21 + override def twentyTwo: Int = 21 + override def thirty: Int = 21 + +inline trait T13 extends T0: + override def thirteen: Int = 13 + override def twentyTwo: Int = 13 + override def thirty: Int = 13 + +inline trait T22 extends T12, T13: + override def twentyTwo: Int = 22 + override def thirty: Int = 22 + +class C extends T21, T22: + override def thirty: Int = 30 diff --git a/tests/run/inline-trait-inheritance-inline-ancestors/normaltraits.scala b/tests/run/inline-trait-inheritance-inline-ancestors/normaltraits.scala new file mode 100644 index 000000000000..ba128145c793 --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-ancestors/normaltraits.scala @@ -0,0 +1,47 @@ +package normaltraits + +val normalValues: List[Int] = + val c = C() + List(c.zero, c.eleven, c.twelve, c.thirteen, c.twentyOne, c.twentyTwo, c.thirty) + +trait T0: + def zero: Int = 0 + def eleven: Int = 0 + def twelve: Int = 0 + def twentyOne: Int = 0 + def thirteen: Int = 0 + def twentyTwo: Int = 0 + def thirty: Int = 0 + +trait T11 extends T0: + override def eleven: Int = 11 + override def twelve: Int = 11 + override def twentyOne: Int = 11 + override def thirteen: Int = 11 + override def twentyTwo: Int = 11 + override def thirty: Int = 11 + +trait T12 extends T0: + override def twelve: Int = 12 + override def twentyOne: Int = 12 + override def thirteen: Int = 12 + override def twentyTwo: Int = 12 + override def thirty: Int = 12 + +trait T21 extends T11, T12: + override def twentyOne: Int = 21 + override def thirteen: Int = 21 + override def twentyTwo: Int = 21 + override def thirty: Int = 21 + +trait T13 extends T0: + override def thirteen: Int = 13 + override def twentyTwo: Int = 13 + override def thirty: Int = 13 + +trait T22 extends T12, T13: + override def twentyTwo: Int = 22 + override def thirty: Int = 22 + +class C extends T21, T22: + override def thirty: Int = 30 \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-inline-ancestors/test.scala b/tests/run/inline-trait-inheritance-inline-ancestors/test.scala new file mode 100644 index 000000000000..5f7a9be7a41b --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-ancestors/test.scala @@ -0,0 +1,5 @@ +import normaltraits.normalValues +import inlinetraits.inlineValues + +@main def Test = + assert(normalValues == inlineValues) \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-inline-grandparent.check b/tests/run/inline-trait-inheritance-inline-grandparent.check new file mode 100644 index 000000000000..02a3fcae5b4e --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-grandparent.check @@ -0,0 +1,6 @@ +0 +(Test SimpleC,Hello) + +5678 +(Test C,Hello,9) +5678 diff --git a/tests/run/inline-trait-inheritance-inline-grandparent.scala b/tests/run/inline-trait-inheritance-inline-grandparent.scala new file mode 100644 index 000000000000..91d1486d31f8 --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-grandparent.scala @@ -0,0 +1,33 @@ +package simpleGrandParent: + inline trait SimpleGrandParent[T]: + def foo(): Int = 0 + def foooo(): T = ??? + + inline trait SimpleParent[T, U](x: T, z: U) extends SimpleGrandParent[U]: + def bar(a: T) = (a, x) + + class SimpleC extends SimpleParent("Hello", 1234) + +package grandParentWithArgs: + inline trait GrandParent[T](val x: T, val y: T): + def foo(): T = x + def foooo(): T = y + + inline trait Parent[T, U](w: T, z: U) extends GrandParent[U]: + def bar(a: T) = (a, w, y) + + class C extends Parent("Hello", 1234), GrandParent(5678, 9) + +@main def Test = + import simpleGrandParent.SimpleC + import grandParentWithArgs.C + + val simpleC = SimpleC() + println(simpleC.foo()) + println(simpleC.bar("Test SimpleC")) + println() + + val c = C() + println(c.foo()) + println(c.bar("Test C")) + println(c.x) \ No newline at end of file diff --git a/tests/run/inline-trait-maximum-name-clash.scala b/tests/run/inline-trait-maximum-name-clash.scala new file mode 100644 index 000000000000..c37e995636ca --- /dev/null +++ b/tests/run/inline-trait-maximum-name-clash.scala @@ -0,0 +1,17 @@ +inline trait A(x: Int): + val z = x +inline trait B(x: Int): + val y = x +inline trait C(val x: Int) + +class D extends A(314), B(1200), C(12): + override val x = 1 + override val y = x + val w = y + +@main def Test = + val v = D() + assert(v.y == 1) + assert(v.w == 1) + assert(v.z == 314) + assert(v.x == 1) diff --git a/tests/run/inline-trait-nested.scala b/tests/run/inline-trait-nested.scala new file mode 100644 index 000000000000..a3727e9aeb2a --- /dev/null +++ b/tests/run/inline-trait-nested.scala @@ -0,0 +1,16 @@ +// While we don't allow inner nested classes inside inline traits, we do allow creation of anonymous classes inside methods +// inside inline traits - after all these are just ordinary methods. + +inline trait Trait[T]: + def do_something() = println("Good morning") + +inline trait A[T]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int] {} + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/run/inline-trait-override-in-parent-triangle.scala b/tests/run/inline-trait-override-in-parent-triangle.scala new file mode 100644 index 000000000000..bbf06a8244a5 --- /dev/null +++ b/tests/run/inline-trait-override-in-parent-triangle.scala @@ -0,0 +1,14 @@ +inline trait Foo: + def foo = "Foo" + +inline trait Bar extends Foo: + override def foo = "Bar" + +class C extends Bar, Foo +class D extends Foo, Bar + +@main def Test = + val c = C() + val d = D() + assert(c.foo == "Bar") + assert(d.foo == "Bar") diff --git a/tests/run/inline-trait-param-shadows-parent-indirect.scala b/tests/run/inline-trait-param-shadows-parent-indirect.scala new file mode 100644 index 000000000000..964b95f5acfb --- /dev/null +++ b/tests/run/inline-trait-param-shadows-parent-indirect.scala @@ -0,0 +1,15 @@ +inline trait A[T](x: T): + def y = x + +inline trait B extends A[Int] +inline trait D extends A[Int] + +inline trait E extends B +inline trait F extends D + +class C extends E, F, A[Int](100) + +object Test: + def main(args: Array[String]): Unit = + val z = new C + assert(z.y == 100) diff --git a/tests/run/inline-trait-signature-parameters-val-block.check b/tests/run/inline-trait-signature-parameters-val-block.check new file mode 100644 index 000000000000..063724ea6c43 --- /dev/null +++ b/tests/run/inline-trait-signature-parameters-val-block.check @@ -0,0 +1,4 @@ +I am a B! +1 +1 +1 diff --git a/tests/run/inline-trait-signature-parameters-val-block.scala b/tests/run/inline-trait-signature-parameters-val-block.scala new file mode 100644 index 000000000000..794a6071d073 --- /dev/null +++ b/tests/run/inline-trait-signature-parameters-val-block.scala @@ -0,0 +1,10 @@ +inline trait A(val x: Int) + +class B extends A({ println("I am a B!"); 1 }) + +@main() def Test: Unit = { + val b = B() + println(b.x) + println(b.x) + println(b.x) +} \ No newline at end of file diff --git a/tests/run/inline-trait-signature-side-effects-1.check b/tests/run/inline-trait-signature-side-effects-1.check new file mode 100644 index 000000000000..4df3c9a85d7f --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-1.check @@ -0,0 +1,6 @@ +0 +0 +1 +0 +1 +2 diff --git a/tests/run/inline-trait-signature-side-effects-1.scala b/tests/run/inline-trait-signature-side-effects-1.scala new file mode 100644 index 000000000000..61c348eb48d1 --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-1.scala @@ -0,0 +1,27 @@ +inline trait A(i: Int): + val x = i + val y = i + +class B(i: Int) extends A(i) + +@main def Test = + var c = 0 + + val b1 = new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) + val b2 = new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) + val b3 = new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) \ No newline at end of file diff --git a/tests/run/inline-trait-signature-side-effects-2.check b/tests/run/inline-trait-signature-side-effects-2.check new file mode 100644 index 000000000000..4df3c9a85d7f --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-2.check @@ -0,0 +1,6 @@ +0 +0 +1 +0 +1 +2 diff --git a/tests/run/inline-trait-signature-side-effects-2.scala b/tests/run/inline-trait-signature-side-effects-2.scala new file mode 100644 index 000000000000..19dcad0681a3 --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-2.scala @@ -0,0 +1,16 @@ +inline trait A(i: Int): + val x = i + val y = i + +class B(i: Int) extends A(i) + +@main def Test = + var c = 0 + + for _ <- 0 until 3 + do new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) \ No newline at end of file diff --git a/tests/run/inline-trait-specialized-desugar.scala b/tests/run/inline-trait-specialized-desugar.scala new file mode 100644 index 000000000000..ee8f57ddc91d --- /dev/null +++ b/tests/run/inline-trait-specialized-desugar.scala @@ -0,0 +1,43 @@ +// User code does this: (with Specialized type class) +inline trait Iterator[T]: + def hasNext: Boolean + def next(): T + +// User code does this: (with Specialized type class) +inline trait ArrayIterator[T](elems: Array[T]) extends Iterator[T]: + private var current: Int = 0 + def hasNext: Boolean = current < elems.length + def next(): T = try elems(current) finally current += 1 + + +// Specialized traits generates these signatures: +inline trait Iterator_sp_Int extends Iterator[Int] +inline trait ArrayIterator_sp_Int extends ArrayIterator[Int], Iterator_sp_Int +class ArrayIterator_impl_Int(elems: Array[Int]) extends ArrayIterator_sp_Int, ArrayIterator[Int](elems) + +// User code does this: +def foo(x: ArrayIterator[Int]): Int = x.next() + +// Specialized traits converts this to +def foo(x: ArrayIterator_sp_Int): Int = x.next() + +// User code does this: +/* class MyClassA + class MyClassB extends MyClassA, ArrayIterator[Int](Array.from(Seq(1, 5))) */ + +// We convert this to: +class MyClassA +class MyClassB extends MyClassA, ArrayIterator_sp_Int, ArrayIterator[Int](Array.from(Seq(1, 5))) + +@main def Test = + val xs: Array[Int] = Array(1, 2, 3) + + // User code does this: + /* val ai = new ArrayIterator[Int](xs) {} */ + + // We convert this to: + val ai = ArrayIterator_impl_Int(xs) + + val mcb = MyClassB() + assert(mcb.hasNext) + assert(ai.next() == 1) diff --git a/tests/run/inline-trait-super-call.scala b/tests/run/inline-trait-super-call.scala new file mode 100644 index 000000000000..e2e335799599 --- /dev/null +++ b/tests/run/inline-trait-super-call.scala @@ -0,0 +1,24 @@ +inline trait A: + def foo = "A" + def bar = "bar" + +inline trait B: + def foo = "B" + +class C extends A, B: + override def foo = super.foo + +class D extends A, B: + override def foo = super[A].foo + +class E extends A, B: + override def foo = super[B].foo + def baz = super[A].bar // No override; this also needs to work + +@main def Test: Unit = + val c = C() + assert(c.foo == "B") + val d = D() + assert(d.foo == "A") + val e = E() + assert(e.foo == "B") diff --git a/tests/run/inline-trait-super-chain.scala b/tests/run/inline-trait-super-chain.scala new file mode 100644 index 000000000000..53ba24d8aa72 --- /dev/null +++ b/tests/run/inline-trait-super-chain.scala @@ -0,0 +1,23 @@ +inline trait A: + def foo = "A" + +inline trait B extends A: + override def foo = super.foo + +inline trait C extends B: + override def foo = "B" + +inline trait D extends C: + override def foo = super.foo + +inline trait E extends D: + override def foo = super.foo + +inline trait F extends E: + override def foo = super.foo + +class C1 extends F + +@main def Test = + val cl = C1() + assert(cl.foo == "B") diff --git a/tests/run/inline-trait-varargs.scala b/tests/run/inline-trait-varargs.scala new file mode 100644 index 000000000000..e88d1cd8f96c --- /dev/null +++ b/tests/run/inline-trait-varargs.scala @@ -0,0 +1,7 @@ +inline trait ListA[T](vals: T*): + def printVals() = + vals.foreach(println(_)) + +@main def Test = + val x = new ListA[Short](4, 6, 7, 8, 10, 5, 11, 100) {} + x.printVals() diff --git a/tests/run/specialized-trait-all-general-raw.scala b/tests/run/specialized-trait-all-general-raw.scala new file mode 100644 index 000000000000..f63bdc6d2565 --- /dev/null +++ b/tests/run/specialized-trait-all-general-raw.scala @@ -0,0 +1,21 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[A: Specialized, B: Specialized, C: Specialized, D: Specialized, E: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +inline trait Bar[A: Specialized, B, E]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +inline trait Baz[A: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +@main def Test = + val foo = new Foo[Any, AnyVal, Object, AnyRef, Nothing]() {} + println(foo.foo) + assert(foo.foo == "Foo$impl") + + val bar = new Bar[AnyVal, Int, String]() {} + assert(bar.foo == "Bar$impl") + + val baz = new Baz[(Int, String)]() {} + + assert(baz.foo == "Baz$impl") diff --git a/tests/run/specialized-trait-argument-specializes-current-trait.scala b/tests/run/specialized-trait-argument-specializes-current-trait.scala new file mode 100644 index 000000000000..b3e067793f2c --- /dev/null +++ b/tests/run/specialized-trait-argument-specializes-current-trait.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](elems: Array[T]): + def length = elems.length + + def apply(i: Int): T = elems(i) + + def lengthOfOtherVector(other: Vec[Int]): Int = + other.length + +object Test: + def main(args: Array[String]) = + val x = new Vec[Int](Array(1, 2, 3, 4, 5)) {} + val y = new Vec[Int](Array(3, 4, 5, 6, 7, 9)) {} + assert(x.lengthOfOtherVector(y) == 6) + assert(y.lengthOfOtherVector(x) == 5) diff --git a/tests/run/specialized-trait-as-parameter.scala b/tests/run/specialized-trait-as-parameter.scala new file mode 100644 index 000000000000..ac5eb130a89b --- /dev/null +++ b/tests/run/specialized-trait-as-parameter.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo(x: Trait[T]) = x + +@main def Test = + val b = new A[Long]() {} + b.foo(new Trait[Long] {}).do_something() diff --git a/tests/run/specialized-trait-as-return-type.scala b/tests/run/specialized-trait-as-return-type.scala new file mode 100644 index 000000000000..2307b766eb0b --- /dev/null +++ b/tests/run/specialized-trait-as-return-type.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo: Trait[T] + +inline trait B[S: Specialized] extends A[S]: + override def foo = new Trait[S] {} + +@main def Test = + val b = new B[Int]() {} + val f = b.foo + f.do_something() diff --git a/tests/run/specialized-trait-as-val.scala b/tests/run/specialized-trait-as-val.scala new file mode 100644 index 000000000000..dcbc06aa2585 --- /dev/null +++ b/tests/run/specialized-trait-as-val.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + val t = new Trait[T] {} + +@main def Test = + val b = new A[Int]() {} + b.t.do_something() diff --git a/tests/run/specialized-trait-body-macro-uses-type-param/Macro_1.scala b/tests/run/specialized-trait-body-macro-uses-type-param/Macro_1.scala new file mode 100644 index 000000000000..593623d38800 --- /dev/null +++ b/tests/run/specialized-trait-body-macro-uses-type-param/Macro_1.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +import scala.quoted.* + +inline def describe[T]: String = ${ describeImpl[T] } + +def describeImpl[T: Type](using Quotes): Expr[String] = + Expr(Type.show[T]) + +inline trait A[T: Specialized]: + val name: String = describe[T] diff --git a/tests/run/specialized-trait-body-macro-uses-type-param/Test_2.scala b/tests/run/specialized-trait-body-macro-uses-type-param/Test_2.scala new file mode 100644 index 000000000000..6d122f4d491e --- /dev/null +++ b/tests/run/specialized-trait-body-macro-uses-type-param/Test_2.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +class B extends A[Int] +class C extends A[String] + +@main def Test = + assert(B().name == "scala.Int", B().name) + assert(C().name == "scala.Predef.String", C().name) diff --git a/tests/run/specialized-trait-call-method-in-other-file/A_1.scala b/tests/run/specialized-trait-call-method-in-other-file/A_1.scala new file mode 100644 index 000000000000..762bd6017223 --- /dev/null +++ b/tests/run/specialized-trait-call-method-in-other-file/A_1.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](val x: T): + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +object Methods: + def foo(y: Foo[Int]) = + assert(y.foo == "Foo$impl$scala$Int") + y.x + + def bar(y: Foo[Boolean]) = + assert(y.foo == "Bar") + y.x diff --git a/tests/run/specialized-trait-call-method-in-other-file/B_2.scala b/tests/run/specialized-trait-call-method-in-other-file/B_2.scala new file mode 100644 index 000000000000..acdd2d2305f1 --- /dev/null +++ b/tests/run/specialized-trait-call-method-in-other-file/B_2.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +class Bar extends Foo[Boolean](true) + +@main def Test = + val x = new Foo[Int](100) {} + val y = Bar() + + assert(Methods.foo(x) == 100) + assert(Methods.bar(y) == true) diff --git a/tests/run/specialized-trait-check-bridge-exists-hidden-by-inline.scala b/tests/run/specialized-trait-check-bridge-exists-hidden-by-inline.scala new file mode 100644 index 000000000000..76f7241fa3a1 --- /dev/null +++ b/tests/run/specialized-trait-check-bridge-exists-hidden-by-inline.scala @@ -0,0 +1,19 @@ +// scalajs: --skip +// (getName() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo(x: Trait[T]): Unit + +inline trait B extends A[Int]: + override def foo(x: Trait[Int]): Unit = x.do_something() + +class C extends B // Should have a bridge for the specialized foo + +@main def Test = + val fooMethods = classOf[C].getDeclaredMethods.filter(_.getName == "foo") + assert(fooMethods.length == 2, s"expected 2 `foo` methods on Bar (real + bridge), found ${fooMethods.length}: ${fooMethods.mkString(", ")}") + diff --git a/tests/run/specialized-trait-check-bridge-exists.scala b/tests/run/specialized-trait-check-bridge-exists.scala new file mode 100644 index 000000000000..eb8a2d27ebd1 --- /dev/null +++ b/tests/run/specialized-trait-check-bridge-exists.scala @@ -0,0 +1,16 @@ +// scalajs: --skip +// (getName() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized]: + def foo(x: Foo[T]): Foo[T] + +class Bar extends Foo[Int]: + override def foo(x: Foo[Int]): Foo[Int] = x + +@main def Test = + val x = Bar() + val fooMethods = classOf[Bar].getDeclaredMethods.filter(_.getName == "foo") + assert(fooMethods.length == 2, s"expected 2 `foo` methods on Bar (real + bridge), found ${fooMethods.length}: ${fooMethods.mkString(", ")}") + diff --git a/tests/run/specialized-trait-check-specialized-method-called.scala b/tests/run/specialized-trait-check-specialized-method-called.scala new file mode 100644 index 000000000000..16f2b0714016 --- /dev/null +++ b/tests/run/specialized-trait-check-specialized-method-called.scala @@ -0,0 +1,26 @@ +//> using options -language:experimental.specializedTraits + +// Check we actually call the specialized method that we should be calling +// We can't easily check the return type, but we can check that we are calling a method +// on Foo$impl$Int$ directly, with no bridge methods in between (this means it's the correct +// method without boxing / unboxing). + +inline trait Foo[T: Specialized](x: T): + def foo = + val stackTrace = Thread.currentThread.getStackTrace() + + // No bridge methods; we call foo directly + assert(stackTrace.toList.tail.takeWhile(call => call.getMethodName().startsWith("foo")).length == 1) + + // We call this method on the correct impl class + assert(Thread.currentThread.getStackTrace()(1).getClassName() == "Foo$impl$scala$Int") + x + +def f(b: Foo[Int]) = + 37 + b.foo + +object Test: + def main(args: Array[String]): Unit = { + val x = new Foo[Int](42) {} + f(x) + } diff --git a/tests/run/specialized-trait-class-extends-specialized-trait-check-specialized.scala b/tests/run/specialized-trait-class-extends-specialized-trait-check-specialized.scala new file mode 100644 index 000000000000..02c470bdd27a --- /dev/null +++ b/tests/run/specialized-trait-class-extends-specialized-trait-check-specialized.scala @@ -0,0 +1,22 @@ +// scalajs: --skip +// (getInterfaces() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized](x: T): + def foo = x + +class Bar extends Foo[Int](10): + def myMethod = "Hello I am a method" + +class Bar2 extends Foo(10): + def myMethod = "Hello I am a method" + +@main def Test = + val y = new Foo(10) {} + val x = Bar() + val traits = classOf[Bar].getInterfaces() + + assert(traits.exists(cl => cl.getName() == "Foo$$sp$scala$Int")) + + val traits2 = classOf[Bar2].getInterfaces() + assert(traits2.exists(cl => cl.getName() == "Foo$$sp$scala$Int")) diff --git a/tests/run/specialized-trait-collections-example.scala b/tests/run/specialized-trait-collections-example.scala new file mode 100644 index 000000000000..3f7e5d7bb099 --- /dev/null +++ b/tests/run/specialized-trait-collections-example.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.specializedTraits + +inline trait Iterator[T: Specialized]: + def hasNext: Boolean + def next(): T + +inline trait ArrayIterator[T: Specialized](elems: Array[T]) extends Iterator[T]: + private var current = 0 + def hasNext: Boolean = current < elems.length + def next(): T = try elems(current) finally current += 1 + +inline trait Iterable[T: Specialized]: + def iterator: Iterator[T] + def forall(f: T => Unit): Unit = + val it = iterator + while it.hasNext do f(it.next()) + +inline trait Seq[T: Specialized](elems: Array[T]) extends Iterable[T]: + def length: Int = elems.length + def apply(i: Int): T = elems(i) + def iterator: Iterator[T] = new ArrayIterator[T](elems) {} + +@main def Test = + val elems = Array.from(scala.collection.immutable.Seq(1, 2, 3, 4, 5)) + val seq = new Seq[Int](elems) {} + var x = 0 + seq.forall(v => x += v) + assert(x == 15) diff --git a/tests/run/specialized-trait-diamond/A_1.scala b/tests/run/specialized-trait-diamond/A_1.scala new file mode 100644 index 000000000000..f7d8db67febf --- /dev/null +++ b/tests/run/specialized-trait-diamond/A_1.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits + +// Lib1 and Lib2 both specialize A, can we share their specializations? +// At the moment we don't. Do we still avoid symbol collisions? + +inline trait A[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() diff --git a/tests/run/specialized-trait-diamond/B_2.scala b/tests/run/specialized-trait-diamond/B_2.scala new file mode 100644 index 000000000000..7e8ebb7f03e0 --- /dev/null +++ b/tests/run/specialized-trait-diamond/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +object B: + val a = new A[Int]() {} \ No newline at end of file diff --git a/tests/run/specialized-trait-diamond/C_3.scala b/tests/run/specialized-trait-diamond/C_3.scala new file mode 100644 index 000000000000..49d99c8c73da --- /dev/null +++ b/tests/run/specialized-trait-diamond/C_3.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +object C: + val a = new A[Int]() {} \ No newline at end of file diff --git a/tests/run/specialized-trait-diamond/D_4.scala b/tests/run/specialized-trait-diamond/D_4.scala new file mode 100644 index 000000000000..7561c8d2f064 --- /dev/null +++ b/tests/run/specialized-trait-diamond/D_4.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + assert(B.a.foo == "A$impl$scala$Int") + assert(C.a.foo == "A$impl$scala$Int") diff --git a/tests/run/specialized-trait-erasure-impl-classes.scala b/tests/run/specialized-trait-erasure-impl-classes.scala new file mode 100644 index 000000000000..1b850f0aad3f --- /dev/null +++ b/tests/run/specialized-trait-erasure-impl-classes.scala @@ -0,0 +1,50 @@ +//> using options -language:experimental.specializedTraits + +class Animal +class BigCat extends Animal +class Lion extends BigCat + +inline trait A[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +abstract class methods: + // Specialization takes place + def bar1(a: A[Int]): Unit = assert(a.foo == "A$impl$scala$Int", a.foo) + def bar2(a: A[String]): Unit = assert(a.foo == "A$impl$java$lang$String", a.foo) + def bar3(a: A[Lion]): Unit = assert(a.foo == "A$impl$Animal", a.foo) + def bar4(a: A[BigCat]): Unit = assert(a.foo == "A$impl$Animal", a.foo) + def bar5(a: A[Animal]): Unit = assert(a.foo == "A$impl$Animal", a.foo) + def bar11(a: A[Boolean]): Unit = assert(a.foo == "A$impl$scala$Boolean", a.foo) + def bar12(a: A[Byte]): Unit = assert(a.foo == "A$impl$scala$Byte", a.foo) + def bar13(a: A[Short]): Unit = assert(a.foo == "A$impl$scala$Short", a.foo) + def bar14(a: A[Long]): Unit = assert(a.foo == "A$impl$scala$Long", a.foo) + def bar15(a: A[Float]): Unit = assert(a.foo == "A$impl$scala$Float", a.foo) + def bar16(a: A[Double]): Unit = assert(a.foo == "A$impl$scala$Double", a.foo) + def bar17(a: A[Char]): Unit = assert(a.foo == "A$impl$scala$Char", a.foo) + + // SpecType is a top class -> no $impl$ class generated, just an anonymous class. + def bar6(a: A[List[Int]]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar7(a: A[IArray[Boolean]]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar8(a: A[Object]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar9(a: A[AnyVal]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar10(a: A[AnyRef]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + +@main def Test = + val m = new methods {} + m.bar1(new A[Int]() {}) + m.bar2(new A[String]() {}) + m.bar3(new A[Lion]() {}) + m.bar4(new A[BigCat]() {}) + m.bar5(new A[Animal]() {}) + m.bar6(new A[List[Int]]() {}) + m.bar7(new A[IArray[Boolean]]() {}) + m.bar8(new A[Object]() {}) + m.bar9(new A[AnyVal]() {}) + m.bar10(new A[AnyRef]() {}) + m.bar11(new A[Boolean]() {}) + m.bar12(new A[Byte]() {}) + m.bar13(new A[Short]() {}) + m.bar14(new A[Long]() {}) + m.bar15(new A[Float]() {}) + m.bar16(new A[Double]() {}) + m.bar17(new A[Char]() {}) diff --git a/tests/run/specialized-trait-erasure.scala b/tests/run/specialized-trait-erasure.scala new file mode 100644 index 000000000000..8496f7d912ce --- /dev/null +++ b/tests/run/specialized-trait-erasure.scala @@ -0,0 +1,48 @@ +// scalajs: --skip +// (getName() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits + +class Animal +class BigCat extends Animal +class Lion extends BigCat + +inline trait A[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +abstract class methods: + def bar1(a: A[Int]): Int // A[Int] -> A$sp$Int + def bar2(a: A[String]): String // A[String] -> A$sp$String + def bar3(a: A[Lion]): Lion // A[Lion] -> A$sp$Animal + def bar4(a: A[BigCat]): Lion // A[BigCat] -> A$sp$Animal + def bar5(a: A[Animal]): Lion // A[Animal] -> A$sp$Animal + + def bar6(a: A[List[Int]]): Lion // A[List[Int]] -> A + def bar7(a: A[IArray[Boolean]]): Lion // A[IArray[Bool]] -> A + def bar8(a: A[Object]): Lion // A[Object] -> A + def bar9(a: A[AnyVal]): Lion // A[AnyVal] -> A + def bar10(a: A[AnyRef]): Lion // A[AnyRef] -> A + +@main def Test = + val expectedParamErasure = Map( + "bar1" -> "A$$sp$scala$Int", + "bar2" -> "A$$sp$java$lang$String", + "bar3" -> "A$$sp$Animal", + "bar4" -> "A$$sp$Animal", + "bar5" -> "A$$sp$Animal", + "bar6" -> "A", + "bar7" -> "A", + "bar8" -> "A", + "bar9" -> "A", + "bar10" -> "A", + ) + + val actualParamErasure = classOf[methods].getDeclaredMethods.iterator + .filter(_.getName.startsWith("bar")) + .map(m => m.getName -> m.getParameterTypes.head.getName) + .toMap + + for (name, expected) <- expectedParamErasure do + val actual = actualParamErasure.getOrElse( + name, sys.error(s"method $name not found on class methods")) + assert(actual == expected, s"$name: expected param type $expected, got $actual") diff --git a/tests/run/specialized-trait-inline-specialization-macro/Macro_1.scala b/tests/run/specialized-trait-inline-specialization-macro/Macro_1.scala new file mode 100644 index 000000000000..3bcdbf6dd476 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialization-macro/Macro_1.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits +import scala.quoted.* + +inline def foo(): Unit = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Unit] = + '{ + val x = new A[Int]() {} + assert(x.foo(15) == 10) + assert(x.bar == "A$impl$scala$Int") + } + +inline trait A[T: Specialized]: + def foo(x: T) = 10 + def bar = Thread.currentThread.getStackTrace()(1).getClassName() diff --git a/tests/run/specialized-trait-inline-specialization-macro/Test_2.scala b/tests/run/specialized-trait-inline-specialization-macro/Test_2.scala new file mode 100644 index 000000000000..527d156fc648 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialization-macro/Test_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + foo() \ No newline at end of file diff --git a/tests/run/specialized-trait-inline-specialized-instance-hidden/A_1.scala b/tests/run/specialized-trait-inline-specialized-instance-hidden/A_1.scala new file mode 100644 index 000000000000..f6ca2ab21d07 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-hidden/A_1.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def bar = + val x = new A[Int]() {} + x.foo() + 5 diff --git a/tests/run/specialized-trait-inline-specialized-instance-hidden/B_2.scala b/tests/run/specialized-trait-inline-specialized-instance-hidden/B_2.scala new file mode 100644 index 000000000000..d57d993d3053 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-hidden/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + assert(bar == 5) diff --git a/tests/run/specialized-trait-inline-specialized-instance-with-specialization/A_1.scala b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/A_1.scala new file mode 100644 index 000000000000..4026e3488c96 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/A_1.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def bar[T: Specialized] = new A[T]() {} diff --git a/tests/run/specialized-trait-inline-specialized-instance-with-specialization/B_2.scala b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/B_2.scala new file mode 100644 index 000000000000..305743252757 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + bar[Int].foo() diff --git a/tests/run/specialized-trait-inline-specialized-instance/A_1.scala b/tests/run/specialized-trait-inline-specialized-instance/A_1.scala new file mode 100644 index 000000000000..35220628cef2 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance/A_1.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def bar = new A[Int]() {} diff --git a/tests/run/specialized-trait-inline-specialized-instance/B_2.scala b/tests/run/specialized-trait-inline-specialized-instance/B_2.scala new file mode 100644 index 000000000000..b9cb802a15a1 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + bar.foo() diff --git a/tests/run/specialized-trait-inlining-loop.scala b/tests/run/specialized-trait-inlining-loop.scala new file mode 100644 index 000000000000..034c1cc256b1 --- /dev/null +++ b/tests/run/specialized-trait-inlining-loop.scala @@ -0,0 +1,25 @@ +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized]: + def bar1 = myInlineMethod1[T] + +inline trait T2[T: Specialized]: + def bar2 = myInlineMethod2[T] + +inline trait T3[T: Specialized]: + def foo(x: T): T = x + +inline def myInlineMethod1[T: Specialized] = new T2[T]() {} +inline def myInlineMethod2[T: Specialized] = new T3[T]() {} + +@main def Test = + val x = new T1[Int]() {} + assert(x.bar1.bar2.foo(10) == 10) + +// Need to: +// 1) Create T1$impl$Int$ and inline `def bar1 = myInlineMethod1[Int]` (desugarSpecializedTraits) +// 2) Inline myInlineMethod1 into bar1 inside T1$impl$Int (inlining) +// 3) Create T2$impl$Int and fix the inlined definition of bar1/myInlineMethod1 (desugarSpecializedTraits) +// 4) Inline myInlineMethod2 into bar2 inside T2$impl$Int +// 5) Create T3$impl$Int and fix the inlined definition of bar2/myInlineMethod2 (desugarSpecializedTraits) + +// This can continue for arbitrarily many inlines and you don't know in advance that any of these specializations need to be created. diff --git a/tests/run/specialized-trait-list-of-specialized-trait.scala b/tests/run/specialized-trait-list-of-specialized-trait.scala new file mode 100644 index 000000000000..446276c0e8c7 --- /dev/null +++ b/tests/run/specialized-trait-list-of-specialized-trait.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +def bar(xs: List[Foo[Int]]) = xs.head + +@main def Test = + val myList = List(new Foo[Int]() {}, new Foo[Int]() {}) + assert(bar(myList).foo == "Foo$impl$scala$Int") diff --git a/tests/run/specialized-trait-list-zip-example.check b/tests/run/specialized-trait-list-zip-example.check new file mode 100644 index 000000000000..d12f0d8f3af1 --- /dev/null +++ b/tests/run/specialized-trait-list-zip-example.check @@ -0,0 +1,4 @@ +The population of Switzerland is 9.1 million and its capital is Bern +The population of France is 68.52 million and its capital is Paris +The population of The Netherlands is 18.4 million and its capital is Berlin +The population of Germany is 83.5 million and its capital is The Hague diff --git a/tests/run/specialized-trait-list-zip-example.scala b/tests/run/specialized-trait-list-zip-example.scala new file mode 100644 index 000000000000..b75eb7848c9b --- /dev/null +++ b/tests/run/specialized-trait-list-zip-example.scala @@ -0,0 +1,45 @@ +//> using options -language:experimental.specializedTraits + +import scala.annotation.nowarn + +@nowarn("id=E233") +sealed inline trait List[+T: Specialized]: + inline def zip[S: Specialized](other: List[S]): List[(T, S)] = + def zip(xxs: List[T], yys: List[S]): List[(T, S)] = (xxs, yys) match { + case (_: Nill[_], _) => Nill() + case (_, _: Nill[_]) => Nill() + case (xxs: :+:[T @unchecked], yys: :+:[S @unchecked]) => :+:((xxs.head, yys.head), zip(xxs.tail, yys.tail)) + } + zip(this, other) + + def foreach[S](f: T => Unit): Unit = (this: List[T]) match { // TODO: Can we avoid the need to cast this to List[T] here? Should it not already be of that type? + case xs: :+:[T @unchecked] => f(xs.head); xs.tail.foreach(f) + case _: Nill[_] => + } + + +sealed inline trait Nill[T: Specialized] extends List[T] +sealed inline trait :+:[T: Specialized](val head: T, val tail: List[T]) extends List[T] + +object Nill { + inline def apply[T: Specialized]() = new Nill[T]() {} +} + +object :+: { + inline def apply[T: Specialized](head: T, tail: List[T]): List[T] = + new :+:[T](head, tail) {} +} + +object List: + inline def apply[T: Specialized](values: T*) = + values.foldRight[List[T]](Nill())(:+:.apply) + +@main def Test = + val xs: List[Double] = :+:(9.1, :+:(68.52, :+:(18.4, :+:(83.5, Nill[Double]())))) // TODO : Can we prevent the need for an explicit type here or at least make it clearer + val ys = List("Switzerland", "France", "The Netherlands", "Germany") + val zs = List("Bern", "Paris", "Berlin", "The Hague") + + xs.zip(ys).zip(zs).foreach( (numberCountry, capital) => + val (number, country) = numberCountry + println(s"The population of ${country} is ${number} million and its capital is ${capital}") + ) diff --git a/tests/run/specialized-trait-manual-class-extend-not-top-of-hierarchy.scala b/tests/run/specialized-trait-manual-class-extend-not-top-of-hierarchy.scala new file mode 100644 index 000000000000..712824b59efd --- /dev/null +++ b/tests/run/specialized-trait-manual-class-extend-not-top-of-hierarchy.scala @@ -0,0 +1,16 @@ +// scalajs: --skip +// (getInterfaces() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits + +class Animal +class Mammal extends Animal +class Binturong extends Mammal + +inline trait A[T: Specialized] + +class B extends A[Binturong] + +@main def Test = + val traits = classOf[B].getInterfaces() + assert(traits.exists(cl => cl.getName() == "A$$sp$Animal")) diff --git a/tests/run/specialized-trait-masked-inline-specialization-via-inlining.scala b/tests/run/specialized-trait-masked-inline-specialization-via-inlining.scala new file mode 100644 index 000000000000..5b21dbe9d3b9 --- /dev/null +++ b/tests/run/specialized-trait-masked-inline-specialization-via-inlining.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def myMethod0[T: Specialized] = new A[T]() {} +inline def myMethod1 = myMethod0[Int] +inline def myMethod2 = myMethod1 +inline def myMethod3 = myMethod2 + +@main def Test = + val v = myMethod3 + v.foo() diff --git a/tests/run/specialized-trait-masked-inline-specialized.scala b/tests/run/specialized-trait-masked-inline-specialized.scala new file mode 100644 index 000000000000..69f1618b8480 --- /dev/null +++ b/tests/run/specialized-trait-masked-inline-specialized.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def myMethod1 = new A[Int]() {} +inline def myMethod2 = myMethod1 +inline def myMethod3 = myMethod2 + +@main def Test = + val v = myMethod3 + v.foo() diff --git a/tests/run/specialized-trait-maths.scala b/tests/run/specialized-trait-maths.scala new file mode 100644 index 000000000000..25ca7da8ceec --- /dev/null +++ b/tests/run/specialized-trait-maths.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](x: T): + def foo = x + +def f(b: Foo[Int]) = 37 + b.foo + +object Test: + def main(args: Array[String]): Unit = { + val x = new Foo[Int](42) {} + val y = f(x) + assert(y == 79) + } diff --git a/tests/run/specialized-trait-multiple-files/A_1.scala b/tests/run/specialized-trait-multiple-files/A_1.scala new file mode 100644 index 000000000000..9fb078f6bccf --- /dev/null +++ b/tests/run/specialized-trait-multiple-files/A_1.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo(x: T):T = x + +class B extends A[Int] diff --git a/tests/run/specialized-trait-multiple-files/B_2.scala b/tests/run/specialized-trait-multiple-files/B_2.scala new file mode 100644 index 000000000000..7843ebadf48f --- /dev/null +++ b/tests/run/specialized-trait-multiple-files/B_2.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +class C extends A[Int] + +@main def Test = + val b = B() + val c = C() + + println(b.foo(10)) + println(c.foo(10)) \ No newline at end of file diff --git a/tests/run/specialized-trait-multiple-package-same-file-nested.scala b/tests/run/specialized-trait-multiple-package-same-file-nested.scala new file mode 100644 index 000000000000..d44ba22053cc --- /dev/null +++ b/tests/run/specialized-trait-multiple-package-same-file-nested.scala @@ -0,0 +1,30 @@ +//> using options -language:experimental.specializedTraits +package owner { + package package1 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] + } + + package package2 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 2!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] + } +} + +@main def Test = + val b = owner.package1.B() + val c = owner.package2.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 2!") + + val d = new owner.package1.A[Int]() {} + val e = new owner.package2.A[Int]() {} + assert(d.bar == "owner.package1.A$impl$scala$Int") + assert(e.bar == "owner.package2.A$impl$scala$Int") + diff --git a/tests/run/specialized-trait-multiple-package-same-file.scala b/tests/run/specialized-trait-multiple-package-same-file.scala new file mode 100644 index 000000000000..be6b83aa5465 --- /dev/null +++ b/tests/run/specialized-trait-multiple-package-same-file.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.specializedTraits + +package package1 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] +} + +package package2 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 2!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] +} + +@main def Test = + val b = package1.B() + val c = package2.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 2!") + + val d = new package1.A[Int]() {} + val e = new package2.A[Int]() {} + assert(d.bar == "package1.A$impl$scala$Int") + assert(e.bar == "package2.A$impl$scala$Int") diff --git a/tests/run/specialized-trait-multiple-parameter-packs-method.scala b/tests/run/specialized-trait-multiple-parameter-packs-method.scala new file mode 100644 index 000000000000..f03d2302ce02 --- /dev/null +++ b/tests/run/specialized-trait-multiple-parameter-packs-method.scala @@ -0,0 +1,20 @@ +//> using options -language:experimental.specializedTraits + + +inline trait A[T: Specialized, S: Numeric](val x: T)(val y: T)(val z: S)(p: S): + def getArgs = (x, y, z, p) + +inline def foo[P: Specialized](x: P)[Q: {Specialized, Numeric}](y: P, z: Q, p: Q) = new A[P, Q](x)(y)(z)(p) {} + +inline trait B[T: Specialized](val w: T): + inline def foo[P: Specialized](x: P, z: P)[Q: Specialized](y: Q, f: P) = + val b = new B[P](x) {} + val c = new B[Q](y) {} + c + +@main def Test = + val a = foo[String]("Good evening")[Long]("Good morning", 1_000_000_000_000_000L, 1_000_000_000_000_001L) + assert(a.getArgs == ("Good evening", "Good morning", 1_000_000_000_000_000L, 1_000_000_000_000_001L)) + + val b = new B[Boolean](true) {} + assert(b.foo[Float](1.1, 2.2)[Short](1, 3.3).w == 1) diff --git a/tests/run/specialized-trait-multiple-parameter-packs.scala b/tests/run/specialized-trait-multiple-parameter-packs.scala new file mode 100644 index 000000000000..eb7710b00601 --- /dev/null +++ b/tests/run/specialized-trait-multiple-parameter-packs.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized, S: Numeric](val x: T)(val y: T)(val z: S)(p: S): + val p2 = p + def getArgs = (x, y, z, p2) + +class B(w: Int)(e: String) extends A[String, Int](e)("Y")(100)(w) + +@main def Test = + val b = B(41)("Good Morning") + assert(b.getArgs == ("Good Morning", "Y", 100, 41)) + + val a = new A[Boolean, Long](true)(false)(1000000000)(-10) {} + assert(a.getArgs == (true, false, 1000000000, -10)) diff --git a/tests/run/specialized-trait-one-parent-is-inline.scala b/tests/run/specialized-trait-one-parent-is-inline.scala new file mode 100644 index 000000000000..e9a6fd56f831 --- /dev/null +++ b/tests/run/specialized-trait-one-parent-is-inline.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized] +inline trait A[T](x: T): + def foo: T = x + +class B extends A[Int](15), Foo: + val y = 1 + +def h(x: B) = x.foo + +@main def Test = + val b = B() + assert(h(b) == 15) + assert(b.y == 1) diff --git a/tests/run/specialized-trait-package-contains-object-multi-file/A_1.scala b/tests/run/specialized-trait-package-contains-object-multi-file/A_1.scala new file mode 100644 index 000000000000..0810149ba75e --- /dev/null +++ b/tests/run/specialized-trait-package-contains-object-multi-file/A_1.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits +package package1 { + object Outer: + object Inner: + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends Outer.Inner.A[Int] + class C extends Outer.Inner.A[String] +} + diff --git a/tests/run/specialized-trait-package-contains-object-multi-file/B_2.scala b/tests/run/specialized-trait-package-contains-object-multi-file/B_2.scala new file mode 100644 index 000000000000..b7984b4d8708 --- /dev/null +++ b/tests/run/specialized-trait-package-contains-object-multi-file/B_2.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + val b = package1.B() + val c = package1.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 1!") + + val d = new package1.Outer.Inner.A[Int]() {} + println(d.bar) + assert(d.bar == "package1.Outer$Inner$$A$impl$scala$Int") diff --git a/tests/run/specialized-trait-package-contains-object.scala b/tests/run/specialized-trait-package-contains-object.scala new file mode 100644 index 000000000000..0489a1e9baa8 --- /dev/null +++ b/tests/run/specialized-trait-package-contains-object.scala @@ -0,0 +1,20 @@ +//> using options -language:experimental.specializedTraits +package package1 { + object Outer: + object Inner: + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends Outer.Inner.A[Int] + class C extends Outer.Inner.A[String] +} + +@main def Test = + val b = package1.B() + val c = package1.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 1!") + + val d = new package1.Outer.Inner.A[Int]() {} + assert(d.bar == "package1.Outer$Inner$$A$impl$scala$Int") + diff --git a/tests/run/specialized-trait-package/A_1.scala b/tests/run/specialized-trait-package/A_1.scala new file mode 100644 index 000000000000..57eecffa6c17 --- /dev/null +++ b/tests/run/specialized-trait-package/A_1.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +package package1 + +inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + \ No newline at end of file diff --git a/tests/run/specialized-trait-package/A_2.scala b/tests/run/specialized-trait-package/A_2.scala new file mode 100644 index 000000000000..268ee13bffe8 --- /dev/null +++ b/tests/run/specialized-trait-package/A_2.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits + +package package2 + +inline trait A[T: Specialized]: + def foo(x: T) = "Package 2!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() diff --git a/tests/run/specialized-trait-package/B_3.scala b/tests/run/specialized-trait-package/B_3.scala new file mode 100644 index 000000000000..93a5c9ba801b --- /dev/null +++ b/tests/run/specialized-trait-package/B_3.scala @@ -0,0 +1,21 @@ +//> using options -language:experimental.specializedTraits +class B extends package1.A[Int] +class C extends package2.A[Int] + +@main def Test = + val b = B() + val c = C() + assert(b.foo(10) == "Package 1!") + assert(c.foo(11) == "Package 2!") + + val d = new package1.A[Int]() {} + val e = new package2.A[Int]() {} + assert(d.bar == "package1.A$impl$scala$Int") + assert(e.bar == "package2.A$impl$scala$Int") + + import package1.A + import package2.A as A2 + val f = new A[Int]() {} + val g = new A2[Int]() {} + assert(d.bar == "package1.A$impl$scala$Int") + assert(e.bar == "package2.A$impl$scala$Int") diff --git a/tests/run/specialized-trait-pathological-context-bounds.scala b/tests/run/specialized-trait-pathological-context-bounds.scala new file mode 100644 index 000000000000..d299704034eb --- /dev/null +++ b/tests/run/specialized-trait-pathological-context-bounds.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +trait A[T, R, Q] + +inline trait Trait[T: {Specialized, Numeric}, S <: Object, Q: Numeric, R: Specialized, D: {Numeric, Specialized}](a: Int) extends A[S, Char, T] { + def bar = "Buna saira" +} + +def foo(v: Trait[Int, String, Int, Int, Int]) = v + +object Test: + def main(args: Array[String]): Unit = { + val a = new Trait[Int, String, Int, Int, Int](5) {} + assert(foo(a).bar == "Buna saira") + } + diff --git a/tests/run/specialized-trait-requires-inline-trait-inlining.scala b/tests/run/specialized-trait-requires-inline-trait-inlining.scala new file mode 100644 index 000000000000..2f5532f93e7c --- /dev/null +++ b/tests/run/specialized-trait-requires-inline-trait-inlining.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +inline trait T1[T]: + def boo(x: T): T = x + +inline trait T[T: Specialized]: + def ff = new T1[T]() { + def id(x: T): T = x + } + +@main def Test = + val a = new T[Int]() {} diff --git a/tests/run/specialized-trait-scoped-inside-object-2.scala b/tests/run/specialized-trait-scoped-inside-object-2.scala new file mode 100644 index 000000000000..d6c53971f589 --- /dev/null +++ b/tests/run/specialized-trait-scoped-inside-object-2.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +object MySpecializedStuff: + inline trait Foo[T: Specialized]: + def bar = "Bar" + + class Bar extends Foo[Char] + def foo(x: Foo[Char]) = x.bar + +@main def Test = + val x = new MySpecializedStuff.Bar() + assert(MySpecializedStuff.foo(x) == "Bar") diff --git a/tests/run/specialized-trait-scoped-inside-object-3.scala b/tests/run/specialized-trait-scoped-inside-object-3.scala new file mode 100644 index 000000000000..adec56fcfd66 --- /dev/null +++ b/tests/run/specialized-trait-scoped-inside-object-3.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +object MySpecializedStuff: + inline trait Foo[T: Specialized]: + def bar = "Bar" + +object MySpecializedStuff2: + class Bar extends MySpecializedStuff.Foo[Char] + def foo(x: MySpecializedStuff.Foo[Char]) = x.bar + +@main def Test = + val x = new MySpecializedStuff2.Bar() + assert(MySpecializedStuff2.foo(x) == "Bar") diff --git a/tests/run/specialized-trait-scoped-inside-object.scala b/tests/run/specialized-trait-scoped-inside-object.scala new file mode 100644 index 000000000000..ec14264a72a7 --- /dev/null +++ b/tests/run/specialized-trait-scoped-inside-object.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +object MySpecializedStuff: + inline trait Foo[T: Specialized]: + def bar = "Bar" + + def foo = new Foo[Int] {} + +@main def Test = MySpecializedStuff.foo.bar diff --git a/tests/run/specialized-trait-self-inline.check b/tests/run/specialized-trait-self-inline.check new file mode 100644 index 000000000000..ce0fe52fb9c7 --- /dev/null +++ b/tests/run/specialized-trait-self-inline.check @@ -0,0 +1,6 @@ +5 +4 +3 +2 +1 +Lift Off! diff --git a/tests/run/specialized-trait-self-inline.scala b/tests/run/specialized-trait-self-inline.scala new file mode 100644 index 000000000000..bc454581d160 --- /dev/null +++ b/tests/run/specialized-trait-self-inline.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.specializedTraits +//> using scala 3.9.0-RC1-bin-SNAPSHOT-nonbootstrapped + +import math.Numeric.Implicits.infixNumericOps +import math.Ordering.Implicits.infixOrderingOps + +inline trait Foo[T: {Specialized, Numeric}]: + inline def foo[S: Specialized](inline x: Int, inline y: Int, inline w: T, inline z: S): S = + inline if x < y then + z + else + println(w) + foo(x - 1, y, w - summon[Numeric[T]].one, z) + +@main def Test = + val x = new Foo[Short] {} + println(x.foo(10, 6, 5, "Lift Off!")) diff --git a/tests/run/specialized-trait-subtyping-once-specialized.scala b/tests/run/specialized-trait-subtyping-once-specialized.scala new file mode 100644 index 000000000000..9ae5fbd742fa --- /dev/null +++ b/tests/run/specialized-trait-subtyping-once-specialized.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] +inline trait T2[T: Specialized] extends T1[T] + +def foo(x: T1[Int]) = println("foo") + +@main def Test = + val x = new T2[Int]() {} + foo(x) diff --git a/tests/run/specialized-trait-super-chain.scala b/tests/run/specialized-trait-super-chain.scala new file mode 100644 index 000000000000..73bd635f83e9 --- /dev/null +++ b/tests/run/specialized-trait-super-chain.scala @@ -0,0 +1,46 @@ +//> using options -language:experimental.specializedTraits +var hops = 0 + +inline trait A[T: Specialized]: + def foo() = + hops += 1 + "A" + +inline trait B[T: Specialized] extends A[T]: + override def foo() = + hops += 1 + super.foo() + +inline trait C[T: Specialized] extends B[T]: + override def foo() = + hops += 1 + "B" + +inline trait D[T: Specialized] extends C[T]: + override def foo() = + hops += 1 + super.foo() + +inline trait E[T: Specialized] extends D[T]: + override def foo() = + hops += 1 + super.foo() + +inline trait F[T: Specialized] extends E[T]: + override def foo() = + hops += 1 + super.foo() + +class C1 extends F[Int] + +@main def Test = + hops = 0 + val cl = C1() + assert(cl.foo() == "B") + assert(hops == 4) + + hops = 0 + val cl2 = new F[String]() {} + assert(cl.foo() == "B") + assert(hops == 4) + diff --git a/tests/run/specialized-trait-varargs.scala b/tests/run/specialized-trait-varargs.scala new file mode 100644 index 000000000000..ca5affc8fa2f --- /dev/null +++ b/tests/run/specialized-trait-varargs.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait ListA[T: Specialized](vals: T*): + def printVals() = + vals.foreach(println(_)) + +@main def Test = + val bungle: List[Short] = List(4123, 6, 7, 8, 10, 5, 11, 100) + val x = new ListA[Short](bungle*) {} + x.printVals() diff --git a/tests/run/specialized-trait-variance.scala b/tests/run/specialized-trait-variance.scala new file mode 100644 index 000000000000..e10d5caf8a0b --- /dev/null +++ b/tests/run/specialized-trait-variance.scala @@ -0,0 +1,53 @@ +//> using options -language:experimental.specializedTraits + +import scala.annotation.nowarn + +trait Animal: + def makeNoise: String +class Lion extends Animal: + override def makeNoise = "ROAR!" +class Dog extends Animal: + override def makeNoise: String = "BARK!" + +trait Material +class Paper extends Material +class Newspaper extends Paper + +inline trait MyList[+T: Specialized](val xs: List[T]): + def map[S](f: T => S) = xs.map(f) + +inline trait Bin[-T: Specialized]: + def throwAway(x: T) = println(s"Throwing away ${x}") + +def sound(animals: MyList[Animal]) = + animals.map(_.makeNoise) + +def throwAwayTheNewspaper(bin: Bin[Newspaper]) = + val newspaper = Newspaper() + bin.throwAway(newspaper) + +def throwAwayAnInteger(bin: Bin[Int]) = + val integer = 100 + bin.throwAway(integer) + +def throwAwayAnObject(bin: Bin[Object]) = + val obj = "good morning" + bin.throwAway(obj) + +def throwAwayAnAnyVal(bin: Bin[AnyVal]) = + val bc: AnyVal = 400 + bin.throwAway(bc) + +@main def Test = + val myDogs: MyList[Dog] = new MyList(List(Dog(), Dog(), Dog())) {} + sound(myDogs) // MyList[Dog] can be interpreted as MyList[Animal] due to covariance + + val myWastepaperBasket = new Bin[Paper]() {} + throwAwayTheNewspaper(myWastepaperBasket) // Bin[Paper] can be interpreted as Bin[Newspaper] due to contravariance + + // Fine; same erasure bucket + val myAnyBin = new Bin[Any] {} + val myAnyRefBin = new Bin[Any] {} + throwAwayAnObject(myAnyBin) + throwAwayAnObject(myAnyRefBin) + throwAwayAnAnyVal(myAnyBin) diff --git a/tests/run/specialized-trait-vector-dot-product.scala b/tests/run/specialized-trait-vector-dot-product.scala new file mode 100644 index 000000000000..4d2112f2b185 --- /dev/null +++ b/tests/run/specialized-trait-vector-dot-product.scala @@ -0,0 +1,38 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: {Specialized, Numeric2}](elems: Array[T]): + private val num = summon[Numeric2[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: Vec[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +object Vec: + inline def apply[T: {Specialized, Numeric2}](elems: Array[T]) = new Vec[T](elems) {} +end Vec + +object Test: + def main(args: Array[String]) = + implicit val v: Numeric2[Int] = new IntIsIntegral() {} + val x = Vec[Int](Array(1, 2, 3, 4, 5)) + val y = Vec[Int](Array(3, 4, 5, 6, 7)) + val z = x.scalarProduct(y) + assert(z == 85) + +inline trait Numeric2[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +class IntIsIntegral extends Numeric2[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y + diff --git a/tests/run/specialized-trait-vector-zip.check b/tests/run/specialized-trait-vector-zip.check new file mode 100644 index 000000000000..d017a5333fe6 --- /dev/null +++ b/tests/run/specialized-trait-vector-zip.check @@ -0,0 +1,4 @@ +The population of Switzerland is 9.1 million and its capital is Bern +The population of France is 68.52 million and its capital is Paris +The population of The Netherlands is 18.4 million and its capital is The Hague +The population of Germany is 83.5 million and its capital is Berlin diff --git a/tests/run/specialized-trait-vector-zip.scala b/tests/run/specialized-trait-vector-zip.scala new file mode 100644 index 000000000000..b9b4bddcef57 --- /dev/null +++ b/tests/run/specialized-trait-vector-zip.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](val xs: List[T]): + inline def zip[S: Specialized](other: Vec[S]): Vec[(T, S)] = + new Vec[(T, S)](xs.zip(other.xs)) {} + +@main def Test = + val xs = new Vec[Double](List(9.1, 68.52, 18.4, 83.5)) {} + val ys = new Vec[String](List("Switzerland", "France", "The Netherlands", "Germany")) {} + val zs = new Vec[String](List("Bern", "Paris", "The Hague", "Berlin")) {} + + xs.zip(ys).zip(zs).xs.map( (numberCountry, capital) => + val (number, country) = numberCountry + println(s"The population of ${country} is ${number} million and its capital is ${capital}") + )