diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 7907201c718e..509e9c6991e7 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -99,7 +99,7 @@ trait ConstraintHandling { val bound = dropWildcards(rawBound) val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param) val equalBounds = (if isUpper then lo else hi) eq bound - if equalBounds && !bound.existsPart(_ eq param, stopAtStatic = true) then + if equalBounds && !bound.existsPart(_ eq param, StopAt.Static) then // The narrowed bounds are equal and not recursive, // so we can remove `param` from the constraint. constraint = constraint.replace(param, bound) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index ba094c587038..b24a4a7bae2c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2546,7 +2546,7 @@ object SymDenotations { } private[SymDenotations] def stillValidInOwner(denot: SymDenotation)(using Context): Boolean = try - val owner = denot.owner.denot + val owner = denot.maybeOwner.denot stillValid(owner) && ( !owner.isClass diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index f183e0467571..8aabed73b8ef 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1391,7 +1391,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling */ def canCompare(ts: Set[Type]) = ctx.phase.isTyper - || !ts.exists(_.existsPart(_.isInstanceOf[SkolemType], stopAtStatic = true)) + || !ts.exists(_.existsPart(_.isInstanceOf[SkolemType], StopAt.Static)) def verified(result: Boolean): Boolean = if Config.checkAtomsComparisons then diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e0c1c35e850a..80e0601a68ee 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -439,14 +439,14 @@ object Types { /** Does this type contain wildcard types? */ final def containsWildcardTypes(using Context) = - existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) + existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false) // ----- Higher-order combinators ----------------------------------- /** Returns true if there is a part of this type that satisfies predicate `p`. */ - final def existsPart(p: Type => Boolean, stopAtStatic: Boolean = false, forceLazy: Boolean = true)(using Context): Boolean = - new ExistsAccumulator(p, stopAtStatic, forceLazy).apply(false, this) + final def existsPart(p: Type => Boolean, stopAt: StopAt = StopAt.None, forceLazy: Boolean = true)(using Context): Boolean = + new ExistsAccumulator(p, stopAt, forceLazy).apply(false, this) /** Returns true if all parts of this type satisfy predicate `p`. */ @@ -454,8 +454,8 @@ object Types { !existsPart(!p(_)) /** Performs operation on all parts of this type */ - final def foreachPart(p: Type => Unit, stopAtStatic: Boolean = false)(using Context): Unit = - new ForeachAccumulator(p, stopAtStatic).apply((), this) + final def foreachPart(p: Type => Unit, stopAt: StopAt = StopAt.None)(using Context): Unit = + new ForeachAccumulator(p, stopAt).apply((), this) /** The parts of this type which are type or term refs and which * satisfy predicate `p`. @@ -5199,6 +5199,12 @@ object Types { // ----- TypeMaps -------------------------------------------------------------------- + /** Where a traversal should stop */ + enum StopAt: + case None // traverse everything + case Package // stop at package references + case Static // stop at static references + /** Common base class of TypeMap and TypeAccumulator */ abstract class VariantTraversal: protected[core] var variance: Int = 1 @@ -5211,7 +5217,7 @@ object Types { res } - protected def stopAtStatic: Boolean = true + protected def stopAt: StopAt = StopAt.Static /** Can the prefix of this static reference be omitted if the reference * itself can be omitted? Overridden in TypeOps#avoid. @@ -5220,7 +5226,11 @@ object Types { protected def stopBecauseStaticOrLocal(tp: NamedType)(using Context): Boolean = (tp.prefix eq NoPrefix) - || stopAtStatic && tp.currentSymbol.isStatic && isStaticPrefix(tp.prefix) + || { + val stop = stopAt + stop == StopAt.Static && tp.currentSymbol.isStatic && isStaticPrefix(tp.prefix) + || stop == StopAt.Package && tp.currentSymbol.is(Package) + } end VariantTraversal abstract class TypeMap(implicit protected var mapCtx: Context) @@ -5409,7 +5419,7 @@ object Types { derivedClassInfo(tp, this(tp.prefix)) def andThen(f: Type => Type): TypeMap = new TypeMap { - override def stopAtStatic = thisMap.stopAtStatic + override def stopAt = thisMap.stopAt def apply(tp: Type) = f(thisMap(tp)) } } @@ -5831,12 +5841,12 @@ object Types { class ExistsAccumulator( p: Type => Boolean, - override val stopAtStatic: Boolean, + override val stopAt: StopAt, forceLazy: Boolean)(using Context) extends TypeAccumulator[Boolean]: def apply(x: Boolean, tp: Type): Boolean = x || p(tp) || (forceLazy || !tp.isInstanceOf[LazyRef]) && foldOver(x, tp) - class ForeachAccumulator(p: Type => Unit, override val stopAtStatic: Boolean)(using Context) extends TypeAccumulator[Unit] { + class ForeachAccumulator(p: Type => Unit, override val stopAt: StopAt)(using Context) extends TypeAccumulator[Unit] { def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp) } diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 8684d7316963..1a70fb1e9d2d 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -700,7 +700,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } // Cannot use standard `existsPart` method because it calls `lookupRefined` // which can cause CyclicReference errors. - val isBoundAccumulator = new ExistsAccumulator(isBound, stopAtStatic = true, forceLazy = true): + val isBoundAccumulator = new ExistsAccumulator(isBound, StopAt.Static, forceLazy = true): override def foldOver(x: Boolean, tp: Type): Boolean = tp match case tp: TypeRef => applyToPrefix(x, tp) case _ => super.foldOver(x, tp) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d51a28a2c51f..f357a4d2441d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -78,9 +78,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } override def nameString(name: Name): String = - if ctx.settings.YdebugNames.value then name.debugString + def strippedName = if printDebug then name else name.stripModuleClassSuffix + if ctx.settings.YdebugNames.value then strippedName.debugString else if name.isTypeName && name.is(WildcardParamName) && !printDebug then "_" - else super.nameString(name) + else super.nameString(strippedName) override protected def simpleNameString(sym: Symbol): String = nameString(if (ctx.property(XprintMode).isEmpty) sym.initial.name else sym.name) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 6abc4ccfd090..e1415f908cb8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1088,7 +1088,7 @@ trait Checking { } case _ => } - tp.foreachPart(check, stopAtStatic = true) + tp.foreachPart(check, StopAt.Static) if (ok) tp else UnspecifiedErrorType } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ed8b347679b5..d3e6c9102a46 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -732,7 +732,7 @@ trait ImplicitRunInfo: case null => record(i"implicitScope") val liftToAnchors = new TypeMap: - override def stopAtStatic = true + override def stopAt = StopAt.Static private val seen = util.HashSet[Type]() def applyToUnderlying(t: TypeProxy) = diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 973df2e5756c..aee1c5040075 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -556,7 +556,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) else inlineCallPrefix - val binding = ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span) + val binding = accountForOpaques( + ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span)) bindingsBuf += binding inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") lastSelf = selfSym @@ -564,10 +565,84 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { } } + /** A list of pairs between TermRefs appearing in thisProxy bindings that + * refer to objects with opaque type aliases and local proxy symbols + * that contain refined versions of these TermRefs where the aliases + * are exposed. + */ + private val opaqueProxies = new mutable.ListBuffer[(TermRef, TermRef)] + + /** Map first halfs of opaqueProxies pairs to second halfs, using =:= as equality */ + def mapRef(ref: TermRef): Option[TermRef] = + opaqueProxies.collectFirst { + case (from, to) if from.symbol == ref.symbol && from =:= ref => to + } + + /** If `binding` contains TermRefs that refer to objects with opaque + * type aliases, add proxy definitions that expose these aliases + * and substitute such TermRefs with theproxies. Example from pos/opaque-inline1.scala: + * + * object refined: + * opaque type Positive = Int + * inline def Positive(value: Int): Positive = f(value) + * def f(x: Positive): Positive = x + * def run: Unit = { val x = 9; val nine = refined.Positive(x) } + * + * This generates the following proxies: + * + * val $proxy1: refined.type{type Positive = Int} = + * refined.$asInstanceOf$[refined.type{type Positive = Int}] + * val refined$_this: ($proxy1 : refined.type{Positive = Int}) = + * $proxy1 + * + * and every reference to `refined` in the inlined expression is replaced by + * `refined_$this`. + */ + def accountForOpaques(binding: ValDef)(using Context): ValDef = + binding.symbol.info.foreachPart { + case ref: TermRef => + for cls <- ref.widen.classSymbols do + if cls.containsOpaques && mapRef(ref).isEmpty then + def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match + case RefinedType(parent, rname, TypeAlias(alias)) => + val opaq = cls.info.member(rname).symbol + if opaq.isOpaqueAlias then + (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) + :: openOpaqueAliases(parent) + else Nil + case _ => + Nil + val refinements = openOpaqueAliases(cls.givenSelfType) + val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) => + RefinedType(parent, refinement._1, TypeAlias(refinement._2)) + ) + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm + val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType)).withSpan(binding.span) + inlining.println(i"add opaque alias proxy $refiningDef") + bindingsBuf += refiningDef + opaqueProxies += ((ref, refiningSym.termRef)) + case _ => + } + if opaqueProxies.isEmpty then binding + else + val mapType = new TypeMap: + override def stopAt = StopAt.Package + def apply(t: Type) = mapOver { + t match + case ref: TermRef => mapRef(ref).getOrElse(ref) + case _ => t + } + binding.symbol.info = mapType(binding.symbol.info) + val mapTree = TreeTypeMap(typeMap = mapType) + mapTree.transform(binding).asInstanceOf[ValDef] + .showing(i"transformed this binding exposing opaque aliases: $result", inlining) + end accountForOpaques + private def canElideThis(tpe: ThisType): Boolean = - inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || - tpe.cls.isContainedIn(inlinedMethod) || - tpe.cls.is(Package) + inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) + || tpe.cls.isContainedIn(inlinedMethod) + || tpe.cls.is(Package) + || tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && inlinedMethod.isContainedIn(tpe.cls)) /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: * - synthetic case class apply methods, when the case class constructor is empty, are @@ -666,12 +741,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case _ => } + private val registerTypes = new TypeTraverser: + override def stopAt = StopAt.Package + override def traverse(t: Type) = + registerType(t) + traverseChildren(t) + /** Register type of leaf node */ - private def registerLeaf(tree: Tree): Unit = tree match { - case _: This | _: Ident | _: TypeTree => - tree.typeOpt.foreachPart(registerType, stopAtStatic = true) + private def registerLeaf(tree: Tree): Unit = tree match + case _: This | _: Ident | _: TypeTree => registerTypes.traverse(tree.typeOpt) case _ => - } /** Make `tree` part of inlined expansion. This means its owner has to be changed * from its `originalOwner`, and, if it comes from outside the inlined method @@ -797,6 +876,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { 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)) @@ -915,7 +996,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { // Take care that only argument bindings go into `bindings`, since positions are // different for bindings from arguments and bindings from body. - tpd.Inlined(call, finalBindings, finalExpansion) + val res = tpd.Inlined(call, finalBindings, finalExpansion) + if opaqueProxies.isEmpty then res + else + val target = + if inlinedMethod.is(Transparent) then call.tpe & res.tpe + else call.tpe + res.ensureConforms(target) + // Make sure that the sealing with the declared type + // is type correct. Without it we might get problems since the + // expression's type is the opaque alias but the call's type is + // the opaque type itself. An example is in pos/opaque-inline1.scala. } } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0f6f7e46a39a..6a47cf143d62 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1519,7 +1519,7 @@ class Namer { typer: Typer => approxTp.stripPoly match case atp @ defn.ContextFunctionType(_, resType, _) if !defn.isNonRefinedFunction(atp) // in this case `resType` is lying, gives us only the non-dependent upper bound - || resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) => + || resType.existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false) => originalTp case _ => approxTp diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 5b52fa883403..b919b87af085 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -259,8 +259,6 @@ object PrepareInlineable { } private def checkInlineMethod(inlined: Symbol, body: Tree)(using Context): body.type = { - if (inlined.owner.isClass && inlined.owner.seesOpaques) - report.error(em"Implementation restriction: No inline methods allowed where opaque type aliases are in scope", inlined.srcPos) if Inliner.inInlineMethod(using ctx.outer) then report.error(ex"Implementation restriction: nested inline methods are not supported", inlined.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 536a80626380..902f8506c870 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2803,7 +2803,7 @@ class Typer extends Namer // see tests/pos/i7778b.scala val paramTypes = { - val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true)) + val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], StopAt.Static)) if hasWildcard then formals.map(_ => untpd.TypeTree()) else formals.map(untpd.TypeTree) } diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 6ac894800616..615e8f3b4049 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -42,7 +42,7 @@ class CompletionTest { @Test def completionFromSyntheticPackageObject: Unit = { code"class Foo { val foo: IArr${m1} }".withSource - .completion(m1, Set(("IArray", Module, "IArray$"), + .completion(m1, Set(("IArray", Module, "IArray"), ("IArray", Field, "scala.IArray"))) } @@ -50,7 +50,7 @@ class CompletionTest { code"class Foo { val foo: Runn${m1} }".withSource .completion(m1, Set( ("Runnable", Class, "java.lang.Runnable"), - ("Runnable", Module, "Runnable$") + ("Runnable", Module, "Runnable") )) } @@ -121,7 +121,7 @@ class CompletionTest { withSources( code"""object O { object MyObject }""", code"""import O.My${m1}""" - ).completion(m1, Set(("MyObject", Module, "O.MyObject$"))) + ).completion(m1, Set(("MyObject", Module, "O.MyObject"))) } @Test def importCompleteWithClassAndCompanion: Unit = { @@ -132,7 +132,7 @@ class CompletionTest { code"""package pgk1 import pkg0.F${m1}""" ).completion(m1, Set(("Foo", Class, "pkg0.Foo"), - ("Foo", Module, "pkg0.Foo$"))) + ("Foo", Module, "pkg0.Foo"))) } @Test def importCompleteIncludePackage: Unit = { @@ -157,7 +157,7 @@ class CompletionTest { ).completion(m1, Set(("myVal", Field, "Int"), ("myDef", Method, "=> Int"), ("myVar", Variable, "Int"), - ("myObject", Module, "MyObject.myObject$"), + ("myObject", Module, "MyObject.myObject"), ("myClass", Class, "MyObject.myClass"), ("myTrait", Class, "MyObject.myTrait"))) } @@ -165,7 +165,7 @@ class CompletionTest { @Test def importJavaClass: Unit = { code"""import java.io.FileDesc${m1}""".withSource .completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"), - ("FileDescriptor", Module, "java.io.FileDescriptor$"))) + ("FileDescriptor", Module, "java.io.FileDescriptor"))) } @Test def importJavaStaticMethod: Unit = { @@ -190,13 +190,13 @@ class CompletionTest { code"""object O { val out = java.io.FileDesc${m1} }""".withSource - .completion(m1, Set(("FileDescriptor", Module, "java.io.FileDescriptor$"))) + .completion(m1, Set(("FileDescriptor", Module, "java.io.FileDescriptor"))) } @Test def importRename: Unit = { code"""import java.io.{FileDesc${m1} => Foo}""".withSource .completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"), - ("FileDescriptor", Module, "java.io.FileDescriptor$"))) + ("FileDescriptor", Module, "java.io.FileDescriptor"))) } @Test def importGivenByType: Unit = { @@ -257,14 +257,14 @@ class CompletionTest { | object bat | val bizz: ba${m1} |}""".withSource - .completion(m1, Set(("bar", Field, "Bar"), ("bat", Module, "Foo.bat$"))) + .completion(m1, Set(("bar", Field, "Bar"), ("bat", Module, "Foo.bat"))) } @Test def completionOnRenamedImport: Unit = { code"""import java.io.{FileDescriptor => AwesomeStuff} trait Foo { val x: Awesom$m1 }""".withSource .completion(m1, Set(("AwesomeStuff", Class, "java.io.FileDescriptor"), - ("AwesomeStuff", Module, "java.io.FileDescriptor$"))) + ("AwesomeStuff", Module, "java.io.FileDescriptor"))) } @Test def completionOnRenamedImport2: Unit = { @@ -274,7 +274,7 @@ class CompletionTest { val x: MyImp$m1 }""".withSource .completion(m1, Set(("MyImportedSymbol", Class, "java.io.FileDescriptor"), - ("MyImportedSymbol", Module, "java.io.FileDescriptor$"))) + ("MyImportedSymbol", Module, "java.io.FileDescriptor"))) } @Test def completionRenamedAndOriginalNames: Unit = { @@ -284,9 +284,9 @@ class CompletionTest { | val x: Hash$m1 |}""".withSource .completion(m1, Set(("HashMap", Class, "java.util.HashMap"), - ("HashMap", Module, "java.util.HashMap$"), + ("HashMap", Module, "java.util.HashMap"), ("HashMap2", Class, "java.util.HashMap"), - ("HashMap2", Module, "java.util.HashMap$"))) + ("HashMap2", Module, "java.util.HashMap"))) } @Test def completionRenamedThrice: Unit = { @@ -297,11 +297,11 @@ class CompletionTest { | val x: MyHash$m1 |}""".withSource .completion(m1, Set(("MyHashMap", Class, "java.util.HashMap"), - ("MyHashMap", Module, "java.util.HashMap$"), + ("MyHashMap", Module, "java.util.HashMap"), ("MyHashMap2", Class, "java.util.HashMap"), - ("MyHashMap2", Module, "java.util.HashMap$"), + ("MyHashMap2", Module, "java.util.HashMap"), ("MyHashMap3", Class, "java.util.HashMap"), - ("MyHashMap3", Module, "java.util.HashMap$"))) + ("MyHashMap3", Module, "java.util.HashMap"))) } @Test def completeFromWildcardImports: Unit = { @@ -372,7 +372,7 @@ class CompletionTest { code"""object Test { | def x = Tes$m1 |}""".withSource - .completion(m1, Set(("Test", Module, "Test$"))) + .completion(m1, Set(("Test", Module, "Test"))) } @Test def completeBothDefinitionsForEqualNestingLevels: Unit = { @@ -524,9 +524,9 @@ class CompletionTest { | val ZZZZ = YY$m1 | type ZZZZ = YY$m2 |}""".withSource - .completion(m1, Set(("YYYY", Field, "Int$"))) + .completion(m1, Set(("YYYY", Field, "Int"))) .completion(m2, Set(("YYYY", Field, "XXXX.YYYY"), - ("YYYY", Field, "Int$"))) + ("YYYY", Field, "Int"))) } @Test def completeRespectingAccessModifiers: Unit = { diff --git a/tests/neg-scalajs/js-type-bad-parents.check b/tests/neg-scalajs/js-type-bad-parents.check index 0375f2fd30c0..ae5e23566f8e 100644 --- a/tests/neg-scalajs/js-type-bad-parents.check +++ b/tests/neg-scalajs/js-type-bad-parents.check @@ -45,7 +45,7 @@ -- Error: tests/neg-scalajs/js-type-bad-parents.scala:36:7 ------------------------------------------------------------- 36 |object C3 extends ScalaClass with js.Any // error |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |C3$ extends ScalaClass which does not extend js.Any. + |C3 extends ScalaClass which does not extend js.Any. -- Error: tests/neg-scalajs/js-type-bad-parents.scala:39:6 ------------------------------------------------------------- 38 |@js.native 39 |trait C4 extends ScalaClass with js.Any // error @@ -60,7 +60,7 @@ 42 |@js.native @JSGlobal 43 |object C6 extends ScalaClass with js.Any // error |^ - |C6$ extends ScalaClass which does not extend js.Any. + |C6 extends ScalaClass which does not extend js.Any. -- Error: tests/neg-scalajs/js-type-bad-parents.scala:45:6 ------------------------------------------------------------- 45 |trait C7 extends js.Object with ScalaTrait // error |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ -- Error: tests/neg-scalajs/js-type-bad-parents.scala:47:7 ------------------------------------------------------------- 47 |object C9 extends js.Object with ScalaTrait // error |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |C9$ extends ScalaTrait which does not extend js.Any. + |C9 extends ScalaTrait which does not extend js.Any. -- Error: tests/neg-scalajs/js-type-bad-parents.scala:50:6 ------------------------------------------------------------- 49 |@js.native 50 |trait C10 extends js.Object with ScalaTrait // error @@ -87,4 +87,4 @@ 53 |@js.native @JSGlobal 54 |object C12 extends js.Object with ScalaTrait // error |^ - |C12$ extends ScalaTrait which does not extend js.Any. + |C12 extends ScalaTrait which does not extend js.Any. diff --git a/tests/neg/exports.check b/tests/neg/exports.check index 8eeea9f5db8d..8ba85f183ce7 100644 --- a/tests/neg/exports.check +++ b/tests/neg/exports.check @@ -11,7 +11,7 @@ 25 | export printUnit.bitmap // error: no eligible member | ^ | non-private given instance bitmap in class Copier refers to private value printUnit - | in its type signature => Copier.this.printUnit.bitmap$ + | in its type signature => Copier.this.printUnit.bitmap -- [E120] Naming Error: tests/neg/exports.scala:23:33 ------------------------------------------------------------------ 23 | export printUnit.{stat => _, _} // error: double definition | ^ diff --git a/tests/neg/i6662.scala b/tests/neg/i6662.scala deleted file mode 100644 index ccad0675da71..000000000000 --- a/tests/neg/i6662.scala +++ /dev/null @@ -1,15 +0,0 @@ -opaque type Opt[A >: Null] = A - -extension [A >: Null](x: Opt[A]) inline def nonEmpty: Boolean = x.get != null // error: Implementation restriction -extension [A >: Null](x: Opt[A]) inline def isEmpty: Boolean = x.get == null // error: Implementation restriction -extension [A >: Null](x: Opt[A]) inline def isDefined: Boolean = x.nonEmpty // error: Implementation restriction -extension [A >: Null](x: Opt[A]) inline def get: A = Opt.unOpt(x) // error: Implementation restriction - -object Opt -{ - inline def unOpt[A >: Null](x: Opt[A]): A = x // error: Implementation restriction - inline def apply[A >: Null](x: A): Opt[A] = x // error: Implementation restriction - inline def some[A >: Null](x: A): Opt[A] = x // error: Implementation restriction - inline def none[A >: Null]: Opt[A] = null // error: Implementation restriction - inline def fromOption[A >: Null](x: Option[A]) = x.orNull // error: Implementation restriction -} diff --git a/tests/pos/i6662.scala b/tests/pos/i6662.scala new file mode 100644 index 000000000000..af0c939c7ed9 --- /dev/null +++ b/tests/pos/i6662.scala @@ -0,0 +1,20 @@ +object opt: + opaque type Opt[A >: Null] = A + object Opt: + inline def unOpt[A >: Null](x: Opt[A]): A = x + inline def apply[A >: Null](x: A): Opt[A] = x + inline def some[A >: Null](x: A): Opt[A] = x + inline def none[A >: Null]: Opt[A] = null + inline def fromOption[A >: Null](x: Option[A]) = x.orNull + +import opt.Opt +extension [A >: Null](x: Opt[A]) + inline def nonEmpty : Boolean = x.get != null + inline def isEmpty : Boolean = x.get == null + inline def isDefined: Boolean = x.nonEmpty + inline def get : A = Opt.unOpt(x) + +@main def Test = + val x: Opt[String] = Opt.some("abc") + assert(x.nonEmpty) + val y: String = Opt.unOpt(x) diff --git a/tests/neg/i6854.scala b/tests/pos/i6854.scala similarity index 69% rename from tests/neg/i6854.scala rename to tests/pos/i6854.scala index 5eb0f8bf6028..78ba9c19af7e 100644 --- a/tests/neg/i6854.scala +++ b/tests/pos/i6854.scala @@ -7,6 +7,6 @@ object Lib { opaque type IArray2[+T] = Array[_ <: T] object IArray2 { - inline def apply(x: =>Int): IArray2[Int] = Array(x) // error + inline def apply(x: =>Int): IArray2[Int] = Array(x) } } diff --git a/tests/neg/inline3.scala b/tests/pos/inline3.scala similarity index 58% rename from tests/neg/inline3.scala rename to tests/pos/inline3.scala index 90a8ea9bb788..d702ca4d7d47 100644 --- a/tests/neg/inline3.scala +++ b/tests/pos/inline3.scala @@ -4,15 +4,13 @@ object K0 { opaque type ProductInstances[F[_], T] = ErasedProductInstances[F[T]] - inline def summonAsArray[F[_], T]: Array[Any] = ??? // error: Implementation restriction: No inline methods allowed - - inline def mkProductInstances[F[_], T]: ProductInstances[F, T] = // error: Implementation restriction: No inline methods allowed + inline def summonAsArray[F[_], T]: Array[Any] = ??? + inline def mkProductInstances[F[_], T]: ProductInstances[F, T] = new ErasedProductInstances(summonAsArray[F, T]).asInstanceOf[ProductInstances[F, T]] val x: T = "" - inline def foo(x: T): T = "foo".asInstanceOf[T] // error: Implementation restriction: No inline methods allowed - + inline def foo(x: T): T = "foo".asInstanceOf[T] } final class ErasedProductInstances[FT](is0: => Array[Any]) @@ -21,7 +19,6 @@ trait Monoid[A] case class ISB(i: Int) object Test { - //val K0 = new K0 K0.foo(K0.x) K0.mkProductInstances[Monoid, ISB] diff --git a/tests/pos/opaque-inline.scala b/tests/pos/opaque-inline.scala new file mode 100644 index 000000000000..a0279f02fbfe --- /dev/null +++ b/tests/pos/opaque-inline.scala @@ -0,0 +1,20 @@ + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + inline def apply(value: Int): Positive = value + + def f(x: Positive): Positive = x + inline def fapply(value: Int): Positive = + val vv = (value, value) // error: implementation restriction + f(vv._1) + +@main def run: Unit = + import refined.* + val x = 9 + val nine = Positive.apply(x) + val nine1 = Positive.fapply(x) + diff --git a/tests/pos/opaque-inline1-transparent.scala b/tests/pos/opaque-inline1-transparent.scala new file mode 100644 index 000000000000..9f05bcc34f85 --- /dev/null +++ b/tests/pos/opaque-inline1-transparent.scala @@ -0,0 +1,11 @@ + +object refined: + opaque type Positive = Int + transparent inline def Positive(value: Int): Positive = f(value) + def f(x: Positive): Positive = x + +object test: + def run: Unit = + val x = 9 + val nine = refined.Positive(x) + diff --git a/tests/pos/opaque-inline1.scala b/tests/pos/opaque-inline1.scala new file mode 100644 index 000000000000..b30eeafdbe77 --- /dev/null +++ b/tests/pos/opaque-inline1.scala @@ -0,0 +1,13 @@ + +object refined: + opaque type Positive = Int + inline def Positive(value: Int): Positive = f(value) + transparent inline def TPositive(value: Int): Positive = f(value) + def f(x: Positive): Positive = x + +object test: + def run: Unit = + val x = 9 + val nine = refined.Positive(x) + val tnine: refined.Positive = refined.TPositive(x) + diff --git a/tests/pos/opaque-inline2-transparent.scala b/tests/pos/opaque-inline2-transparent.scala new file mode 100644 index 000000000000..b0474c57b19e --- /dev/null +++ b/tests/pos/opaque-inline2-transparent.scala @@ -0,0 +1,28 @@ + +import compiletime.* + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + transparent inline def apply(inline value: Int): Positive = + inline if value < 0 then error(codeOf(value) + " is not positive.") + else value + + transparent inline def safe(value: Int): Positive | IllegalArgumentException = + if value < 0 then IllegalArgumentException(s"$value is not positive") + else value: Positive + +@main def Test: Unit = + import refined.* + val eight = Positive(8) + // val negative = Positive(-1) // This correctly produces a compile error "-1 is not positive." + // val random = Positive(scala.util.Random.nextInt()) // This correctly produces a compile error about being unable to inline the method call + val random = Positive.safe(scala.util.Random.nextInt()) + val safeNegative = Positive.safe(-1) + val safeFive = Positive.safe(5) + println(eight) + println(random) + println(safeFive) \ No newline at end of file diff --git a/tests/pos/opaque-inline2.check b/tests/pos/opaque-inline2.check new file mode 100644 index 000000000000..1a3888c02635 --- /dev/null +++ b/tests/pos/opaque-inline2.check @@ -0,0 +1,3 @@ +8 +java.lang.IllegalArgumentException: -1502782350 is not positive +5 diff --git a/tests/pos/opaque-inline2.scala b/tests/pos/opaque-inline2.scala new file mode 100644 index 000000000000..fda5f6756827 --- /dev/null +++ b/tests/pos/opaque-inline2.scala @@ -0,0 +1,28 @@ + +import compiletime.* + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + inline def apply(inline value: Int): Positive = + inline if value < 0 then error(codeOf(value) + " is not positive.") + else value + + transparent inline def safe(value: Int): Positive | IllegalArgumentException = + if value < 0 then IllegalArgumentException(s"$value is not positive") + else value: Positive + +@main def Test: Unit = + import refined.* + val eight = Positive(8) + // val negative = Positive(-1) // This correctly produces a compile error "-1 is not positive." + // val random = Positive(scala.util.Random.nextInt()) // This correctly produces a compile error about being unable to inline the method call + val random = Positive.safe(scala.util.Random.nextInt()) + val safeNegative = Positive.safe(-1) + val safeFive = Positive.safe(5) + println(eight) + println(random) + println(safeFive) \ No newline at end of file diff --git a/tests/run/opaque-inline/EmailAddress.scala b/tests/run/opaque-inline/EmailAddress.scala new file mode 100644 index 000000000000..de5036a0ca77 --- /dev/null +++ b/tests/run/opaque-inline/EmailAddress.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +opaque type EmailAddress = String +object EmailAddress extends EmailAddressOps[EmailAddress]: + + given (using s: ToExpr[String]): ToExpr[EmailAddress] = s + + def parse(s: String): Either[String, EmailAddress] = + if (s contains "@") Right(s) + else Left("No @ symbol") diff --git a/tests/run/opaque-inline/EmailAddressOps.scala b/tests/run/opaque-inline/EmailAddressOps.scala new file mode 100644 index 000000000000..217c36dd6724 --- /dev/null +++ b/tests/run/opaque-inline/EmailAddressOps.scala @@ -0,0 +1,33 @@ +import scala.quoted.* + +trait EmailAddressOps[EmailAddressTransparent <: String]: + + inline def apply(inline s: String): EmailAddress = + ${ EmailAddressOps.applyImpl('s) } + + private val pattern = java.util.regex.Pattern.compile("([^@]*)@([^@]*)") + + extension (value: EmailAddressTransparent) + inline def localPart: String = + val matcher = pattern.matcher(value: String) + matcher.matches + matcher.group(1) + inline def domainPart: String = + val matcher = pattern.matcher(value: String) + matcher.matches + matcher.group(2) + +object EmailAddressOps { + def applyImpl(expr: Expr[String])(using Quotes): Expr[EmailAddress] = + import quotes.reflect.* + expr.asTerm match + case Inlined(_, _, Literal(StringConstant(s))) => + EmailAddress.parse(s) match + case Right(email) => Expr(email) + case Left(err) => + report.error(s"Not a valid email address: $err", expr) + '{???} + case _ => + report.error(s"Not a constant", expr) + '{???} +} \ No newline at end of file diff --git a/tests/run/opaque-inline/Test.scala b/tests/run/opaque-inline/Test.scala new file mode 100644 index 000000000000..c228e361e5a9 --- /dev/null +++ b/tests/run/opaque-inline/Test.scala @@ -0,0 +1,2 @@ +@main def Test = + (new TestEmail).run diff --git a/tests/run/opaque-inline/TestEmail.scala b/tests/run/opaque-inline/TestEmail.scala new file mode 100644 index 000000000000..13121f59e1d6 --- /dev/null +++ b/tests/run/opaque-inline/TestEmail.scala @@ -0,0 +1,7 @@ +class TestEmail { + def getDomain(e: EmailAddress): String = e.domainPart + + def run: Unit = + val em = EmailAddress("a@b.c") + assert(getDomain(em) == "b.c") +} diff --git a/tests/run/opaque-inline2.scala b/tests/run/opaque-inline2.scala new file mode 100644 index 000000000000..fda5f6756827 --- /dev/null +++ b/tests/run/opaque-inline2.scala @@ -0,0 +1,28 @@ + +import compiletime.* + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + inline def apply(inline value: Int): Positive = + inline if value < 0 then error(codeOf(value) + " is not positive.") + else value + + transparent inline def safe(value: Int): Positive | IllegalArgumentException = + if value < 0 then IllegalArgumentException(s"$value is not positive") + else value: Positive + +@main def Test: Unit = + import refined.* + val eight = Positive(8) + // val negative = Positive(-1) // This correctly produces a compile error "-1 is not positive." + // val random = Positive(scala.util.Random.nextInt()) // This correctly produces a compile error about being unable to inline the method call + val random = Positive.safe(scala.util.Random.nextInt()) + val safeNegative = Positive.safe(-1) + val safeFive = Positive.safe(5) + println(eight) + println(random) + println(safeFive) \ No newline at end of file