diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 14664af30c9e..3ae3fe0091f4 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -67,13 +67,13 @@ class Compiler { new CheckLoopingImplicits, // Check that implicit defs do not call themselves in an infinite loop new BetaReduce, // Reduce closure applications new InlineVals, // Check right hand-sides of an `inline val`s - new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes + new ExpandSAMs, // Expand single abstract method closures to anonymous classes + new ElimRepeated) :: // Rewrite vararg parameters and arguments List(new init.Checker) :: // Check initialization of objects - List(new ElimRepeated, // Rewrite vararg parameters and arguments + List(new ByNameLambda, // Replace by-name applications with closures new ProtectedAccessors, // Add accessors for protected members new ExtensionMethods, // Expand methods of value classes with extension methods new UncacheGivenAliases, // Avoid caching RHS of simple parameterless given aliases - new ByNameClosures, // Expand arguments to by-name parameters to closures new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new SpecializeApplyMethods, // Adds specialized methods to FunctionN new RefChecks, // Various checks mostly related to abstract members and overriding @@ -83,7 +83,6 @@ class Compiler { new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only) new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts - new ElimByName, // Expand by-name parameter references new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_` diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 8592b6d2e647..fa7ab6f1c99f 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -726,6 +726,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case _ => tree } + /** An anonymous function and a closure node referring to it in a block, without any wrappings */ + object simpleClosure: + def unapply(tree: Tree)(using Context): Option[(DefDef, Closure)] = tree match + case Block((meth : DefDef) :: Nil, closure: Closure) if meth.symbol == closure.meth.symbol => + Some((meth, closure)) + case _ => + None + /** The variables defined by a pattern, in reverse order of their appearance. */ def patVars(tree: Tree)(using Context): List[Symbol] = { val acc = new TreeAccumulator[List[Symbol]] { diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 7aa4491c31de..b54a4938cb38 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -623,6 +623,12 @@ object Trees { def name: Name = bind.name } + /** By-name wrapper; created by Typer and TreUnpickler, eliminated in TreePickler and ByNameLambda */ + case class ByName[-T >: Untyped] private[ast] (expr: Tree[T])(implicit @constructorOnly src: SourceFile) + extends TermTree[T] { + type ThisTree[-T >: Untyped] = ByName[T] + } + /** return expr * where `from` refers to the method or label from which the return takes place * After program transformations this is not necessarily the enclosing method, because @@ -1075,6 +1081,7 @@ object Trees { type InlineMatch = Trees.InlineMatch[T] type CaseDef = Trees.CaseDef[T] type Labeled = Trees.Labeled[T] + type ByName = Trees.ByName[T] type Return = Trees.Return[T] type WhileDo = Trees.WhileDo[T] type Try = Trees.Try[T] @@ -1228,6 +1235,10 @@ object Trees { case tree: Labeled if (bind eq tree.bind) && (expr eq tree.expr) => tree case _ => finalize(tree, untpd.Labeled(bind, expr)(sourceFile(tree))) } + def ByName(tree: Tree)(expr: Tree)(using Context): ByName = tree match { + case tree: ByName if expr eq tree.expr => tree + case _ => finalize(tree, untpd.ByName(expr)(sourceFile(tree))) + } def Return(tree: Tree)(expr: Tree, from: Tree)(using Context): Return = tree match { case tree: Return if (expr eq tree.expr) && (from eq tree.from) => tree case _ => finalize(tree, untpd.Return(expr, from)(sourceFile(tree))) @@ -1411,6 +1422,8 @@ object Trees { cpy.CaseDef(tree)(transform(pat), transform(guard), transform(body)) case Labeled(bind, expr) => cpy.Labeled(tree)(transformSub(bind), transform(expr)) + case ByName(expr) => + cpy.ByName(tree)(transform(expr)) case Return(expr, from) => cpy.Return(tree)(transform(expr), transformSub(from)) case WhileDo(cond, body) => @@ -1556,6 +1569,8 @@ object Trees { this(this(this(x, pat), guard), body) case Labeled(bind, expr) => this(this(x, bind), expr) + case ByName(expr) => + this(x, expr) case Return(expr, from) => this(this(x, expr), from) case WhileDo(cond, body) => diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 1334a4f73016..57b37136beb1 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -143,6 +143,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Labeled(sym: TermSymbol, expr: Tree)(using Context): Labeled = Labeled(Bind(sym, EmptyTree), expr) + def ByName(expr: Tree)(using Context): ByName = + ta.assignType(untpd.ByName(expr), expr) + def Return(expr: Tree, from: Tree)(using Context): Return = ta.assignType(untpd.Return(expr, from)) @@ -698,6 +701,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { override def Labeled(tree: Tree)(bind: Bind, expr: Tree)(using Context): Labeled = ta.assignType(untpdCpy.Labeled(tree)(bind, expr)) + override def ByName(tree: Tree)(expr: Tree)(using Context): ByName = + ta.assignType(untpdCpy.ByName(tree)(expr), expr) + override def Return(tree: Tree)(expr: Tree, from: Tree)(using Context): Return = ta.assignType(untpdCpy.Return(tree)(expr, from)) @@ -955,6 +961,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def ensureApplied(using Context): Tree = if (tree.tpe.widen.isParameterless) tree else tree.appliedToNone + /** If tree is a by-name application `(arg)` return `arg`, otherwise the original tree */ + def dropByName(using Context): Tree = tree match + case ByName(body) => body + case _ => tree + + /** Wrap tree in a by-name application unless it is already one */ + def wrapByName(using Context): Tree = tree match + case ByName(_) => tree + case _ => ByName(tree) + + /** Make sure tree is by-name application if `formal` is a by-name parameter type */ + def alignByName(formal: Type)(using Context) = formal match + case ByNameType(_) if !tree.tpe.widen.isByName => ByName(tree) + case _ => tree + /** `tree == that` */ def equal(that: Tree)(using Context): Tree = if (that.tpe.widen.isRef(defn.NothingClass)) @@ -1108,7 +1129,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def etaExpandCFT(using Context): Tree = def expand(target: Tree, tp: Type)(using Context): Tree = tp match - case defn.ContextFunctionType(argTypes, resType, isErased) => + case defn.ContextFunctionType(argTypes, resType, isErased) if argTypes.nonEmpty => val anonFun = newAnonFun( ctx.owner, MethodType.companion(isContextual = true, isErased = isErased)(argTypes, resType), diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 7e00972f354d..fd47d2e4e6af 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -383,6 +383,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def InlineMatch(selector: Tree, cases: List[CaseDef])(implicit src: SourceFile): Match = new InlineMatch(selector, cases) def CaseDef(pat: Tree, guard: Tree, body: Tree)(implicit src: SourceFile): CaseDef = new CaseDef(pat, guard, body) def Labeled(bind: Bind, expr: Tree)(implicit src: SourceFile): Labeled = new Labeled(bind, expr) + def ByName(expr: Tree)(implicit src: SourceFile): ByName = new ByName(expr) def Return(expr: Tree, from: Tree)(implicit src: SourceFile): Return = new Return(expr, from) def WhileDo(cond: Tree, body: Tree)(implicit src: SourceFile): WhileDo = new WhileDo(cond, body) def Try(expr: Tree, cases: List[CaseDef], finalizer: Tree)(implicit src: SourceFile): Try = new Try(expr, cases, finalizer) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 464c7900a54f..2cb9e8ebb295 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -447,12 +447,6 @@ class Definitions { @tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _)) @tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false)) - /** Marker method to indicate an argument to a call-by-name parameter. - * Created by byNameClosures and elimByName, eliminated by Erasure, - */ - @tu lazy val cbnArg: TermSymbol = enterPolyMethod(OpsPackageClass, nme.cbnArg, 1, - pt => MethodType(List(FunctionOf(Nil, pt.paramRefs(0))), pt.paramRefs(0))) - /** Method representing a throw */ @tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(ThrowableType), NothingType)) @@ -1081,6 +1075,9 @@ class Definitions { } } + final def isByNameClass(sym: Symbol): Boolean = + sym eq ContextFunction0 + final def isCompiletime_S(sym: Symbol)(using Context): Boolean = sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass @@ -1298,6 +1295,7 @@ class Definitions { @tu lazy val Function0: Symbol = FunctionClass(0) @tu lazy val Function1: Symbol = FunctionClass(1) @tu lazy val Function2: Symbol = FunctionClass(2) + @tu lazy val ContextFunction0: Symbol = FunctionClass(0, isContextual = true) def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef = FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef @@ -1544,7 +1542,8 @@ class Definitions { new PerRun(Function2SpecializedReturnTypes.map(_.symbol)) def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean = - paramTypes.length <= 2 && cls.derivesFrom(FunctionClass(paramTypes.length)) + paramTypes.length <= 2 + && (cls.derivesFrom(FunctionClass(paramTypes.length)) || isByNameClass(cls)) && isSpecializableFunctionSAM(paramTypes, retType) /** If the Single Abstract Method of a Function class has this type, is it specializable? */ diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index acd0088f09aa..9356226cdabc 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -111,7 +111,7 @@ object JavaNullInterop { // then its Scala signature will be `def setNames(names: (String|Null)*): Unit`. // This is because `setNames(null)` passes as argument a single-element array containing the value `null`, // and not a `null` array. - !tp.isRef(defn.RepeatedParamClass) + !tp.isRepeatedParam case _ => true }) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a8915cb9d74b..f8c70176482c 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -445,7 +445,6 @@ object StdNames { val bytes: N = "bytes" val canEqual_ : N = "canEqual" val canEqualAny : N = "canEqualAny" - val cbnArg: N = "" val checkInitialized: N = "checkInitialized" val ClassManifestFactory: N = "ClassManifestFactory" val classOf: N = "classOf" diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 95ad0b95b335..187c9c58cf8a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -412,11 +412,13 @@ class TypeApplications(val self: Type) extends AnyVal { /** Translate a type of the form From[T] to either To[T] or To[? <: T] (if `wildcardArg` is set). Keep other types as they are. * `from` and `to` must be static classes, both with one type parameter, and the same variance. - * Do the same for by name types => From[T] and => To[T] + * Do the same for ExprTypes and by-name types => From[T] and => To[T]. */ def translateParameterized(from: ClassSymbol, to: ClassSymbol, wildcardArg: Boolean = false)(using Context): Type = self match { case self @ ExprType(tp) => self.derivedExprType(tp.translateParameterized(from, to)) + case self @ ByNameType(tp) => + self.derivedByNameType(tp.translateParameterized(from, to)) case _ => if (self.derivesFrom(from)) { def elemType(tp: Type): Type = tp.widenDealias match diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 1d75c9ef0019..c93100bcd9fe 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2335,13 +2335,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } case tp1: RecType => tp1.rebind(distributeAnd(tp1.parent, tp2)) - case ExprType(rt1) => - tp2 match { - case ExprType(rt2) => - ExprType(rt1 & rt2) - case _ => - NoType - } case tp1: TypeVar if tp1.isInstantiated => tp1.underlying & tp2 case tp1: AnnotatedType if !tp1.isRefining => @@ -2359,13 +2352,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * The rhs is a proper supertype of the lhs. */ private def distributeOr(tp1: Type, tp2: Type, isSoft: Boolean = true): Type = tp1 match { - case ExprType(rt1) => - tp2 match { - case ExprType(rt2) => - ExprType(lub(rt1, rt2, isSoft = isSoft)) - case _ => - NoType - } case tp1: TypeVar if tp1.isInstantiated => lub(tp1.underlying, tp2, isSoft = isSoft) case tp1: AnnotatedType if !tp1.isRefining => diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 1473bcc559e2..8961e8d50f6a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -603,8 +603,6 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst this(tp.widen) case SuperType(thistpe, supertpe) => SuperType(this(thistpe), this(supertpe)) - case ExprType(rt) => - defn.FunctionType(0) case RefinedType(parent, nme.apply, refinedInfo) if parent.typeSymbol eq defn.PolyFunctionClass => erasePolyFunctionApply(refinedInfo) case tp: TypeProxy => @@ -692,7 +690,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst else defn.TupleXXLClass.typeRef } - /** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s and + /** The erasure of a symbol's info. This is different from `apply` in the way * `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them * to the underlying type. */ @@ -702,14 +700,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst case _ => tp tp1 match case ExprType(rt) => - if sym.is(Param) then apply(tp1) - // Note that params with ExprTypes are eliminated by ElimByName, - // but potentially re-introduced by ResolveSuper, when we add - // forwarders to mixin methods. - // See doc comment for ElimByName for speculation how we could improve this. - else - MethodType(Nil, Nil, - eraseResult(rt.translateFromRepeated(toArray = sourceLanguage.isJava))) + assert(!sym.is(Param)) + MethodType(Nil, Nil, + eraseResult(rt.translateFromRepeated(toArray = sourceLanguage.isJava))) case tp1: PolyType => eraseResult(tp1.resultType) match case rt: MethodType => rt @@ -820,8 +813,6 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst sigName(elem) ++ "[]" case tp: TermRef => sigName(tp.widen) - case ExprType(rt) => - sigName(defn.FunctionOf(Nil, rt)) case tp: TypeVar => val inst = tp.instanceOpt if (inst.exists) sigName(inst) else tpnme.Uninstantiated diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index c9ca98f65f5e..388a933ac420 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -41,6 +41,10 @@ class MissingType(pre: Type, name: Name) extends TypeError { } } +class InvalidPrefix(pre: Type, desig: Designator) extends TypeError: + override def produceMessage(using Context): Message = + i"malformed type: $pre is not a legal prefix for $desig" + class RecursionOverflow(val op: String, details: => String, val previous: Throwable, val weight: Int) extends TypeError { def explanation: String = s"$op $details" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2d02546a6d5e..716536cfe35d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -90,7 +90,7 @@ object Types { * * Note: please keep in sync with copy in `docs/docs/internals/type-system.md`. */ - abstract class Type extends Hashable with printing.Showable { + abstract class Type extends Hashable with printing.Showable: // ----- Tests ----------------------------------------------------- @@ -398,8 +398,10 @@ object Types { (new isGroundAccumulator).apply(true, this) /** Is this a type of a repeated parameter? */ - def isRepeatedParam(using Context): Boolean = - typeSymbol eq defn.RepeatedParamClass + def isRepeatedParam(using Context): Boolean = this match + case ExprType(underlying) => underlying.isRepeatedParam + case ByNameType(underlying) => underlying.isRepeatedParam + case _ => typeSymbol eq defn.RepeatedParamClass /** Is this the type of a method that has a repeated parameter type as * last parameter type? @@ -1517,10 +1519,13 @@ object Types { } /** If this is a repeated type, its element type, otherwise the type itself */ - def repeatedToSingle(using Context): Type = this match { - case tp @ ExprType(tp1) => tp.derivedExprType(tp1.repeatedToSingle) - case _ => if (isRepeatedParam) this.argTypesHi.head else this - } + def repeatedToSingle(using Context): Type = this match + case tp @ ExprType(tp1) => + tp.derivedExprType(tp1.repeatedToSingle) + case tp @ ByNameType(tp1) => + tp.derivedByNameType(tp1.repeatedToSingle) + case _ => + if (isRepeatedParam) this.argTypesHi.head else this // ----- Normalizing typerefs over refined types ---------------------------- @@ -1841,7 +1846,10 @@ object Types { def dropRepeatedAnnot(using Context): Type = dropAnnot(defn.RepeatedAnnot) def annotatedToRepeated(using Context): Type = this match { - case tp @ ExprType(tp1) => tp.derivedExprType(tp1.annotatedToRepeated) + case tp @ ExprType(tp1) => + tp.derivedExprType(tp1.annotatedToRepeated) + case tp @ ByNameType(tp1) => + tp.derivedByNameType(tp1.annotatedToRepeated) case AnnotatedType(tp, annot) if annot matches defn.RepeatedAnnot => val typeSym = tp.typeSymbol.asClass assert(typeSym == defn.SeqClass || typeSym == defn.ArrayClass) @@ -1916,9 +1924,8 @@ object Types { /** Is the `hash` of this type the same for all possible sequences of enclosing binders? */ def hashIsStable: Boolean = true - } - // end Type + end Type // ----- Type categories ---------------------------------------------- @@ -2085,7 +2092,7 @@ object Types { def designator: Designator protected def designator_=(d: Designator): Unit - assert(prefix.isValueType || (prefix eq NoPrefix), s"invalid prefix $prefix") + if !prefix.isValueType && (prefix ne NoPrefix) then throw InvalidPrefix(prefix, designator) private var myName: Name = null private var lastDenotation: Denotation = null @@ -5448,6 +5455,47 @@ object Types { else None } + // ----- ByName Param Type Encoding ------------------------------------------------ + + object ByNameType: + def apply(tp: Type)(using Context): Type = + defn.FunctionOf(Nil, tp, isContextual = true) + def unapply(tp: Type)(using Context): Option[Type] = tp match + case tp @ AppliedType(tycon, arg :: Nil) if defn.isByNameClass(tycon.typeSymbol) => + Some(arg) + case tp @ AnnotatedType(parent, _) => + unapply(parent) + case _ => + None + end ByNameType + + extension (tp: Type) + def isByName(using Context): Boolean = tp match + case tp @ AppliedType(tycon, arg :: Nil) => + defn.isByNameClass(tycon.typeSymbol) + case tp @ AnnotatedType(parent, _) => + parent.isByName + case _ => + false + + def derivedByNameType(arg: Type)(using Context): Type = tp match + case tp @ AppliedType(tycon, arg0 :: Nil) => + assert(defn.isByNameClass(tycon.typeSymbol)) + if arg0 eq arg then tp + else tp.derivedAppliedType(tycon, arg :: Nil) + case tp @ AnnotatedType(parent, annot) => + tp.derivedAnnotatedType(parent.derivedByNameType(arg), annot) + + def widenByName(using Context): Type = tp match + case ByNameType(underlying) => underlying + case _ => tp + + /** widen by-name types or ExprTypes */ + def widenDelayed(using Context): Type = tp match + case ExprType(underlying) => underlying.widenDelayed + case ByNameType(underlying) => underlying.widenDelayed + case _ => tp + // ----- TypeMaps -------------------------------------------------------------------- /** Where a traversal should stop */ @@ -6140,6 +6188,7 @@ object Types { } } + /** Type size as defined in implicit divergence checking */ class TypeSizeAccumulator(using Context) extends TypeAccumulator[Int] { var seen = util.HashSet[Type](initialCapacity = 8) def apply(n: Int, tp: Type): Int = @@ -6148,7 +6197,8 @@ object Types { seen += tp tp match { case tp: AppliedType => - foldOver(n + 1, tp) + if defn.isByNameClass(tp.tycon.typeSymbol) then foldOver(n, tp.args) + else foldOver(n + 1, tp) case tp: RefinedType => foldOver(n + 1, tp) case tp: TypeRef if tp.info.isTypeAlias => @@ -6161,6 +6211,7 @@ object Types { } } + /** Covering set as defined in implicit divergence checking */ class CoveringSetAccumulator(using Context) extends TypeAccumulator[Set[Symbol]] { var seen = util.HashSet[Type](initialCapacity = 8) def apply(cs: Set[Symbol], tp: Type): Set[Symbol] = @@ -6171,7 +6222,9 @@ object Types { case tp if tp.isExactlyAny || tp.isExactlyNothing => cs case tp: AppliedType => - foldOver(cs + tp.typeSymbol, tp) + val tsym = tp.tycon.typeSymbol + if defn.isByNameClass(tsym) then foldOver(cs, tp.args) + else foldOver(cs + tsym, tp) case tp: RefinedType => foldOver(cs + tp.typeSymbol, tp) case tp: TypeRef if tp.info.isTypeAlias => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 8f5910c3dd56..fc0f00cb2864 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -414,17 +414,17 @@ class TreePickler(pickler: TastyPickler) { } } case Apply(fun, args) => - if (fun.symbol eq defn.throwMethod) { + if fun.symbol eq defn.throwMethod then writeByte(THROW) pickleTree(args.head) - } - else { + else writeByte(APPLY) withLength { pickleTree(fun) - args.foreach(pickleTree) + args.foreach(arg => pickleTree(arg.dropByName)) + // (...) applications are re-constituted when unpickling + // based on formal parameter types. } - } case TypeApply(fun, args) => writeByte(TYPEAPPLY) withLength { @@ -456,7 +456,7 @@ class TreePickler(pickler: TastyPickler) { case NamedArg(name, arg) => writeByte(NAMEDARG) pickleName(name) - pickleTree(arg) + pickleTree(arg.dropByName) case Assign(lhs, rhs) => writeByte(ASSIGN) withLength { pickleTree(lhs); pickleTree(rhs) } @@ -491,6 +491,11 @@ class TreePickler(pickler: TastyPickler) { case CaseDef(pat, guard, rhs) => writeByte(CASEDEF) withLength { pickleTree(pat); pickleTree(rhs); pickleTreeUnlessEmpty(guard) } + case ByName(expr) => + assert(false, i"ByName tree is not a method argument: $tree") + // If we do allow ByName types that are not parameters in a future 3.x version, + // we'd have to replace the assert with a -release check that these types are + // not issued in earlier Tasty versions. case Return(expr, from) => writeByte(RETURN) withLength { pickleSymRef(from.symbol); pickleTreeUnlessEmpty(expr) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index af186e825591..cebfe7f59f44 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -882,7 +882,13 @@ class TreeUnpickler(reader: TastyReader, TypeDef(rhs) } case PARAM => - val tpt = readTpt()(using localCtx) + var tpt = readTpt()(using localCtx) + tpt match + case ByNameTypeTree(restpt) if sym.isAllOf(InlineParam) => + // inline by name parmeters are deprecated but can still appear in Tasty up to 3.1 + tpt = restpt + case _ => + sym.info = tpt.tpe assert(nothingButMods(end)) sym.info = tpt.tpe ValDef(tpt) @@ -1136,8 +1142,16 @@ class TreeUnpickler(reader: TastyReader, val (mixId, mixTpe) = ifBefore(end)(readQualId(), (untpd.EmptyTypeIdent, NoType)) tpd.Super(qual, mixId, mixTpe.typeSymbol) case APPLY => + def restoreByName(arg: Tree, formal: Type): Tree = arg match + case NamedArg(name, arg1) => cpy.NamedArg(arg)(name, restoreByName(arg1, formal)) + case _ => arg.alignByName(formal) val fn = readTerm() - tpd.Apply(fn, until(end)(readTerm())) + var args = until(end)(readTerm()) + fn.tpe.widen match + case mt: MethodType => + args = args.zipWithConserve(mt.paramInfos)(restoreByName) + case _ => + tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) case TYPED => diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 305a8a7510df..3d3f718997a7 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -811,7 +811,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } val tycon = select(pre, sym) val args = until(end, () => readTypeRef()) - if (sym == defn.ByNameParamClass2x) ExprType(args.head) + if sym == defn.ByNameParamClass2x then + ByNameType(args.head) else if (ctx.settings.scalajs.value && args.length == 2 && sym.owner == JSDefinitions.jsdefn.ScalaJSJSPackageClass && sym == JSDefinitions.jsdefn.PseudoUnionClass) { // Treat Scala.js pseudo-unions as real unions, this requires a diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index cf5942a178f0..504d56cdb552 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -218,14 +218,20 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val cls = tycon.typeSymbol - if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*" - else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction) - else if tp.tupleArity >= 2 && !printDebug then toTextTuple(tp.tupleElementTypes) + if tycon.isRepeatedParam then + toTextLocal(args.head) ~ "*" + else if defn.isByNameClass(tycon.typeSymbol) && !printDebug then + changePrec(GlobalPrec) { "=> " ~ toText(tp.widenByName) } + else if defn.isFunctionClass(cls) then + toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction) + else if tp.tupleArity >= 2 && !printDebug then + toTextTuple(tp.tupleElementTypes) else if isInfixType(tp) then val l :: r :: Nil = args val opName = tyconName(tycon) toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) } - else Str("") + else + Str("") case _ => Str("") @@ -299,9 +305,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } } - protected def exprToText(tp: ExprType): Text = - "=> " ~ toText(tp.resType) - protected def blockToText[T >: Untyped](block: Block[T]): Text = blockText(block.stats :+ block.expr) @@ -421,7 +424,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case Super(qual: This, mix) => optDotPrefix(qual) ~ keywordStr("super") ~ optText(mix)("[" ~ _ ~ "]") case app @ Apply(fun, args) => - if (fun.hasType && fun.symbol == defn.throwMethod) + if fun.symbol == defn.throwMethod then changePrec (GlobalPrec) { keywordStr("throw ") ~ toText(args.head) } @@ -496,6 +499,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { keywordStr("case ") ~ inPattern(toText(pat)) ~ optText(guard)(keywordStr(" if ") ~ _) ~ " => " ~ caseBlockText(body) case Labeled(bind, expr) => changePrec(GlobalPrec) { toText(bind.name) ~ keywordStr("[") ~ toText(bind.symbol.info) ~ keywordStr("]: ") ~ toText(expr) } + case ByName(expr) => + if printDebug || ctx.settings.YtestPickler.value then + "(" ~ toText(expr) ~ ")" + else + toText(expr) case Return(expr, from) => val sym = from.symbol if (sym.is(Label)) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 3e0e148f7101..b6a4101bf8c8 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -558,8 +558,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case tp: OrType => val s = combineApiTypes(apiType(tp.tp1), apiType(tp.tp2)) withMarker(s, orMarker) - case ExprType(resultType) => - withMarker(apiType(resultType), byNameMarker) case MatchType(bound, scrut, cases) => val s = combineApiTypes(apiType(bound) :: apiType(scrut) :: cases.map(apiType): _*) withMarker(s, matchMarker) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala index 963a153388a3..0eef5c08e3dc 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala @@ -220,10 +220,15 @@ class TypeOps: def loop(tpe: Type): s.Type = tpe match { case t if t.isFromJavaObject => loop(defn.AnyType) + case ExprType(tpe) => val stpe = loop(tpe) s.ByNameType(stpe) + case ByNameType(tpe) => + val stpe = loop(tpe) + s.ByNameType(stpe) + case TypeRef(pre, sym: Symbol) => val spre = if tpe.hasTrivialPrefix then s.Type.Empty else loop(pre) val ssym = sym.symbolName diff --git a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala b/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala deleted file mode 100644 index 51242e7f2dbe..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala +++ /dev/null @@ -1,40 +0,0 @@ -package dotty.tools.dotc -package transform - -import core._ -import Contexts._ -import Symbols._ -import Types._ -import Flags._ -import DenotTransformers.IdentityDenotTransformer -import core.StdNames.nme - -/** This phase translates arguments to call-by-name parameters, using the rules - * - * x ==> x if x is a => parameter - * e.apply() ==> (e) if e is pure - * e ==> (() => e) for all other arguments - * - * where - * - * : [T](() => T): T - * - * is a synthetic method defined in Definitions. Erasure will later strip the wrappers. - */ -class ByNameClosures extends TransformByNameApply with IdentityDenotTransformer { thisPhase => - import ast.tpd._ - - override def phaseName: String = ByNameClosures.name - - override def runsAfterGroupsOf: Set[String] = Set(ExpandSAMs.name) - // ExpanSAMs applied to partial functions creates methods that need - // to be fully defined before converting. Test case is pos/i9391.scala. - - override def mkByNameClosure(arg: Tree, argType: Type)(using Context): Tree = - val meth = newAnonFun(ctx.owner, MethodType(Nil, argType)) - Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase)).withSpan(arg.span) -} - -object ByNameClosures { - val name: String = "byNameClosures" -} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/ByNameLambda.scala b/compiler/src/dotty/tools/dotc/transform/ByNameLambda.scala new file mode 100644 index 000000000000..6ec7bd895b94 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ByNameLambda.scala @@ -0,0 +1,43 @@ +package dotty.tools +package dotc +package transform + +import core._ +import Flags._ +import MegaPhase._, DenotTransformers.IdentityDenotTransformer +import Symbols._, Contexts._, Types._, Decorators._ +import StdNames.nme +import ast.Trees._ +import ast.TreeTypeMap + +/** Rewrite applications `(...)` to context closures `() ?=> ...` */ +class ByNameLambda extends MiniPhase, IdentityDenotTransformer: + thisPhase => + + import ast.tpd._ + + def phaseName: String = ByNameLambda.name + + override def runsAfterGroupsOf: Set[String] = Set(ElimRepeated.name) + // ByNameLambda needs to run in a group after ElimRepeated since ElimRepeated + // works on ByName arguments but not converted closures, and it sees the arguments + // after transformations by subsequent miniphases in the same group. + + override def transformByName(tree: ByName)(using Context): Tree = tree.expr match + case Apply(Select(fn, nme.apply), Nil) if isPurePath(fn) && fn.tpe.widen.isByName => + fn + case _ => + byNameClosure(tree.expr) + + def byNameClosure(body: Tree)(using Context): Block = + val restpe = body.tpe.widenIfUnstable.deskolemized + val meth = newAnonFun(ctx.owner, MethodType(Nil, restpe), coord = body.span) + Closure(meth, _ => body.changeOwnerAfter(ctx.owner, meth, thisPhase), + targetType = defn.ContextFunction0.typeRef.appliedTo(restpe)) + +object ByNameLambda: + import ast.tpd._ + + val name = "byNameLambda" + +end ByNameLambda \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala index 21b994a6d1b8..2107be7be085 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala @@ -20,7 +20,7 @@ class CheckLoopingImplicits extends MiniPhase: override def phaseName: String = CheckLoopingImplicits.name - override def transformValDef(mdef: ValDef)(using Context): Tree = + override def transformValDef(mdef: ValDef)(using Context): Tree = transform(mdef) override def transformDefDef(mdef: DefDef)(using Context): Tree = @@ -45,15 +45,8 @@ class CheckLoopingImplicits extends MiniPhase: checkNotLooping(qual) case Apply(fn, args) => checkNotLooping(fn) - fn.tpe.widen match - case mt: MethodType - // Boolean && and || aren't defined with by-name parameters - // and therefore their type isn't an ExprType, so we exempt them by symbol name - if t.symbol != defn.Boolean_&& && t.symbol != defn.Boolean_|| => - args.lazyZip(mt.paramInfos).foreach { (arg, pinfo) => - if !pinfo.isInstanceOf[ExprType] then checkNotLooping(arg) - } - case _ => + if fn.symbol != defn.Boolean_&& && fn.symbol != defn.Boolean_|| then + args.foreach(checkNotLooping) case TypeApply(fn, _) => checkNotLooping(fn) case Block(stats, expr) => @@ -79,7 +72,7 @@ class CheckLoopingImplicits extends MiniPhase: checkNotLooping(finalizer) case SeqLiteral(elems, _) => elems.foreach(checkNotLooping) - case t: ValDef => + case t: ValDef => checkNotLooping(t.rhs) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala b/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala index 71bae0ec5a6d..653df2543e72 100644 --- a/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala +++ b/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala @@ -43,8 +43,8 @@ class CollectNullableFields extends MiniPhase { override def phaseName: String = CollectNullableFields.name - /** Running after `ElimByName` to see by names as nullable types. */ - override def runsAfter: Set[String] = Set(ElimByName.name) + /** Running after `ByNameLambda` to see by names as nullable types. */ + override def runsAfter: Set[String] = Set(ByNameLambda.name) private sealed trait FieldInfo private case object NotNullable extends FieldInfo diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala deleted file mode 100644 index aeed5ac76d39..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ /dev/null @@ -1,80 +0,0 @@ -package dotty.tools.dotc -package transform - -import core._ -import DenotTransformers.InfoTransformer -import Symbols._ -import Contexts._ -import Types._ -import core.StdNames.nme -import ast.Trees._ - -/** This phase eliminates ExprTypes `=> T` as types of method parameter references, and replaces them b - * nullary function types. More precisely: - * - * For the types of parameter symbols: - * - * => T ==> () => T - * - * For cbn parameter values - * - * x ==> x() - * - * Note: This scheme to have inconsistent types between method types (whose formal types are still - * ExprTypes and parameter valdefs (which are now FunctionTypes) is not pretty. There are two - * other options which have been abandoned or not yet pursued. - * - * Option 1: Transform => T to () => T also in method and function types. The problem with this is - * that is that it requires to look at every type, and this forces too much, causing - * Cyclic Reference errors. Abandoned for this reason. - * - * Option 2: Merge ElimByName with erasure, or have it run immediately before. This has not been - * tried yet. - */ -class ElimByName extends TransformByNameApply with InfoTransformer { - import ast.tpd._ - - override def phaseName: String = ElimByName.name - - override def changesParents: Boolean = true // Only true for by-names - - /** Map `tree` to `tree.apply()` is `ftree` was of ExprType and becomes now a function */ - private def applyIfFunction(tree: Tree, ftree: Tree)(using Context) = - if (isByNameRef(ftree)) { - val tree0 = transformFollowing(tree) - atPhase(next) { tree0.select(defn.Function0_apply).appliedToNone } - } - else tree - - override def transformIdent(tree: Ident)(using Context): Tree = - applyIfFunction(tree, tree) - - override def transformSelect(tree: Select)(using Context): Tree = - applyIfFunction(tree, tree) - - override def transformTypeApply(tree: TypeApply)(using Context): Tree = tree match { - case TypeApply(Select(_, nme.asInstanceOf_), arg :: Nil) => - // tree might be of form e.asInstanceOf[x.type] where x becomes a function. - // See pos/t296.scala - applyIfFunction(tree, arg) - case _ => tree - } - - override def transformValDef(tree: ValDef)(using Context): Tree = - atPhase(next) { - if (exprBecomesFunction(tree.symbol)) - cpy.ValDef(tree)(tpt = tree.tpt.withType(tree.symbol.info)) - else tree - } - - def transformInfo(tp: Type, sym: Symbol)(using Context): Type = tp match { - case ExprType(rt) => defn.FunctionOf(Nil, rt) - case _ => tp - } - - override def infoMayChange(sym: Symbol)(using Context): Boolean = sym.isTerm && exprBecomesFunction(sym) -} - -object ElimByName { - val name: String = "elimByName" -} diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 84a83a1b9b06..802d77dbb96c 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -60,7 +60,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => addVarArgsForwarder(sym, isJavaVarargsOverride, hasAnnotation, parentHasAnnotation) else if hasAnnotation then report.error("A method without repeated parameters cannot be annotated with @varargs", sym.sourcePos) - end + end transformVarArgs super.transform(ref) match case ref1: SymDenotation if ref1.is(Method, butNot = JavaDefined) => @@ -75,6 +75,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => ref1 case ref1 => ref1 + end transform override def infoMayChange(sym: Symbol)(using Context): Boolean = sym.is(Method) @@ -124,19 +125,21 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => tp override def transformApply(tree: Apply)(using Context): Tree = - val args = tree.args.mapConserve { - case arg: Typed if isWildcardStarArg(arg) => - val isJavaDefined = tree.fun.symbol.is(JavaDefined) - val tpe = arg.expr.tpe - if isJavaDefined then - adaptToArray(arg.expr) - else if tpe.derivesFrom(defn.ArrayClass) then - arrayToSeq(arg.expr) - else - arg.expr - case arg => arg - } - cpy.Apply(tree)(tree.fun, args) + val args1 = tree.args.mapConserve(transformArg(_, tree.fun.symbol.is(JavaDefined))) + cpy.Apply(tree)(tree.fun, args1) + + private def transformArg(arg: Tree, toArray: Boolean)(using Context): Tree = arg match + case arg: Typed if isWildcardStarArg(arg) => + val expr = arg.expr + if toArray then + adaptToArray(expr) + else if expr.tpe.derivesFrom(defn.ArrayClass) then + arrayToSeq(expr) + else + expr + case arg @ ByName(arg1) => + cpy.ByName(arg)(transformArg(arg1, toArray)) + case arg => arg private def adaptToArray(tree: Tree)(implicit ctx: Context): Tree = tree match case SeqLiteral(elems, elemtpt) => diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 91214341f650..765d514de5c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -805,49 +805,46 @@ object Erasure { */ override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = val Apply(fun, args) = tree - if fun.symbol == defn.cbnArg then - typedUnadapted(args.head, pt) - else - val origFun = fun.asInstanceOf[tpd.Tree] - val origFunType = origFun.tpe.widen(using preErasureCtx) - val ownArgs = if origFunType.isErasedMethod then Nil else args - val fun1 = typedExpr(fun, AnyFunctionProto) - fun1.tpe.widen match - case mt: MethodType => - val (xmt, // A method type like `mt` but with bunched arguments expanded to individual ones - bunchArgs, // whether arguments are bunched - outers) = // the outer reference parameter(s) - if fun1.isInstanceOf[Apply] then - (mt, fun1.removeAttachment(BunchedArgs).isDefined, Nil) - else - val xmt = expandedMethodType(mt, origFun) - (xmt, xmt ne mt, outer.args(origFun)) - - val args0 = outers ::: ownArgs - val args1 = args0.zipWithConserve(xmt.paramInfos)(typedExpr) - .asInstanceOf[List[Tree]] - - def mkApply(finalFun: Tree, finalArgs: List[Tree]) = - val app = untpd.cpy.Apply(tree)(finalFun, finalArgs) - .withType(applyResultType(xmt, args1)) - if bunchArgs then app.withAttachment(BunchedArgs, ()) else app - - def app(fun1: Tree): Tree = fun1 match - case Block(stats, expr) => - cpy.Block(fun1)(stats, app(expr)) - case Apply(fun2, SeqLiteral(prevArgs, argTpt) :: _) if bunchArgs => - mkApply(fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) - case Apply(fun2, prevArgs) => - mkApply(fun2, prevArgs ++ args1) - case _ if bunchArgs => - mkApply(fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) - case _ => - mkApply(fun1, args1) - - app(fun1) - case t => - if ownArgs.isEmpty then fun1 - else throw new MatchError(i"tree $tree has unexpected type of function $fun/$fun1: $t, was $origFunType, args = $ownArgs") + val origFun = fun.asInstanceOf[tpd.Tree] + val origFunType = origFun.tpe.widen(using preErasureCtx) + val ownArgs = if origFunType.isErasedMethod then Nil else args + val fun1 = typedExpr(fun, AnyFunctionProto) + fun1.tpe.widen match + case mt: MethodType => + val (xmt, // A method type like `mt` but with bunched arguments expanded to individual ones + bunchArgs, // whether arguments are bunched + outers) = // the outer reference parameter(s) + if fun1.isInstanceOf[Apply] then + (mt, fun1.removeAttachment(BunchedArgs).isDefined, Nil) + else + val xmt = expandedMethodType(mt, origFun) + (xmt, xmt ne mt, outer.args(origFun)) + + val args0 = outers ::: ownArgs + val args1 = args0.zipWithConserve(xmt.paramInfos)(typedExpr) + .asInstanceOf[List[Tree]] + + def mkApply(finalFun: Tree, finalArgs: List[Tree]) = + val app = untpd.cpy.Apply(tree)(finalFun, finalArgs) + .withType(applyResultType(xmt, args1)) + if bunchArgs then app.withAttachment(BunchedArgs, ()) else app + + def app(fun1: Tree): Tree = fun1 match + case Block(stats, expr) => + cpy.Block(fun1)(stats, app(expr)) + case Apply(fun2, SeqLiteral(prevArgs, argTpt) :: _) if bunchArgs => + mkApply(fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) + case Apply(fun2, prevArgs) => + mkApply(fun2, prevArgs ++ args1) + case _ if bunchArgs => + mkApply(fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) + case _ => + mkApply(fun1, args1) + + app(fun1) + case t => + if ownArgs.isEmpty then fun1 + else throw new MatchError(i"tree $tree has unexpected type of function $fun/$fun1: $t, was $origFunType, args = $ownArgs") end typedApply // The following four methods take as the proto-type the erasure of the pre-existing type, diff --git a/compiler/src/dotty/tools/dotc/transform/EtaReduce.scala b/compiler/src/dotty/tools/dotc/transform/EtaReduce.scala index 8afdb5121e19..587cd6be54c5 100644 --- a/compiler/src/dotty/tools/dotc/transform/EtaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/EtaReduce.scala @@ -27,8 +27,7 @@ class EtaReduce extends MiniPhase: override def phaseName: String = "etaReduce" override def transformBlock(tree: Block)(using Context): Tree = tree match - case Block((meth : DefDef) :: Nil, closure: Closure) - if meth.symbol == closure.meth.symbol => + case simpleClosure(meth, closure) => meth.rhs match case Apply(Select(fn, nme.apply), args) if meth.paramss.head.corresponds(args)((param, arg) => diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 9c50e3c2f114..867732d97167 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -235,6 +235,7 @@ object ExplicitOuter { * fully qualified name `outerAccName` will fail, because the `outerAccName`'s * result is phase dependent. In that case we use a backup strategy where we search all * definitions in the class to find the one with the OuterAccessor flag. + * ^^^ check whether this is still needed */ def outerAccessor(cls: ClassSymbol)(using Context): Symbol = if (cls.isStatic) NoSymbol // fast return to avoid scanning package decls diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 18b028d1a024..e96c97cef55c 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -282,13 +282,10 @@ object GenericSignatures { else jsig(erasure(tp), toplevel, primitiveOK) - case ExprType(restpe) if toplevel => + case ExprType(restpe) => builder.append("()") methodResultSig(restpe) - case ExprType(restpe) => - jsig(defn.FunctionType(0).appliedTo(restpe)) - case PolyType(tparams, mtpe: MethodType) => assert(tparams.nonEmpty) if (toplevel && !sym0.isConstructor) polyParamSig(tparams) diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index e1bcc7e6a9b4..90556d458d17 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -45,10 +45,8 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase def phaseName: String = HoistSuperArgs.name - override def runsAfter: Set[String] = Set(ByNameClosures.name) - // By name closures need to be introduced first in order to be hoisted out here. - // There's an interaction with by name closures in that the marker - // application should not be hoisted, but be left at the point of call. + override def runsAfter: Set[String] = Set(ByNameLambda.name) + // Assumes by-name argments are already converted to closures. /** Defines methods for hoisting complex supercall arguments out of * parent super calls and constructor definitions. @@ -63,7 +61,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase * @param cdef The definition of the constructor from which the call is made * @return The argument after possible hoisting */ - private def hoistSuperArg(arg: Tree, cdef: DefDef): Tree = { + private def hoistSuperArg(arg: Tree, cdef: DefDef): Tree = val constr = cdef.symbol lazy val origParams = // The parameters that can be accessed in the supercall if (constr == cls.primaryConstructor) @@ -125,54 +123,50 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase } // begin hoistSuperArg - arg match { - case Apply(fn, arg1 :: Nil) if fn.symbol == defn.cbnArg => - cpy.Apply(arg)(fn, hoistSuperArg(arg1, cdef) :: Nil) - case _ if arg.existsSubTree(needsHoist) => - val superMeth = newSuperArgMethod(arg.tpe) - val superArgDef = DefDef(superMeth, prefss => { - val paramSyms = prefss.flatten.map(pref => - if pref.isType then pref.tpe.typeSymbol else pref.symbol) - val tmap = new TreeTypeMap( - typeMap = new TypeMap { - lazy val origToParam = origParams.zip(paramSyms).toMap - def apply(tp: Type) = tp match { - case tp: NamedType if needsRewire(tp) => - origToParam.get(tp.symbol) match { - case Some(mappedSym) => if (tp.symbol.isType) mappedSym.typeRef else mappedSym.termRef - case None => mapOver(tp) - } - case _ => - mapOver(tp) - } - }, - treeMap = { - case tree: RefTree if needsRewire(tree.tpe) => - cpy.Ident(tree)(tree.name).withType(tree.tpe) - case tree => - tree - }) - tmap(arg).changeOwnerAfter(constr, superMeth, thisPhase) - }) - superArgDefs += superArgDef - def termParamRefs(tp: Type, params: List[Symbol]): List[List[Tree]] = tp match { - case tp: PolyType => - termParamRefs(tp.resultType, params) - case tp: MethodType => - val (thisParams, otherParams) = params.splitAt(tp.paramNames.length) - thisParams.map(ref) :: termParamRefs(tp.resultType, otherParams) - case _ => - Nil - } - val (typeParams, termParams) = origParams.span(_.isType) - val res = ref(superMeth) - .appliedToTypes(typeParams.map(_.typeRef)) - .appliedToArgss(termParamRefs(constr.info, termParams)) - report.log(i"hoist $arg, cls = $cls = $res") - res - case _ => arg - } - } + if arg.existsSubTree(needsHoist) then + val superMeth = newSuperArgMethod(arg.tpe) + val superArgDef = DefDef(superMeth, prefss => { + val paramSyms = prefss.flatten.map(pref => + if pref.isType then pref.tpe.typeSymbol else pref.symbol) + val tmap = new TreeTypeMap( + typeMap = new TypeMap { + lazy val origToParam = origParams.zip(paramSyms).toMap + def apply(tp: Type) = tp match { + case tp: NamedType if needsRewire(tp) => + origToParam.get(tp.symbol) match { + case Some(mappedSym) => if (tp.symbol.isType) mappedSym.typeRef else mappedSym.termRef + case None => mapOver(tp) + } + case _ => + mapOver(tp) + } + }, + treeMap = { + case tree: RefTree if needsRewire(tree.tpe) => + cpy.Ident(tree)(tree.name).withType(tree.tpe) + case tree => + tree + }) + tmap(arg).changeOwnerAfter(constr, superMeth, thisPhase) + }) + superArgDefs += superArgDef + def termParamRefs(tp: Type, params: List[Symbol]): List[List[Tree]] = tp match { + case tp: PolyType => + termParamRefs(tp.resultType, params) + case tp: MethodType => + val (thisParams, otherParams) = params.splitAt(tp.paramNames.length) + thisParams.map(ref) :: termParamRefs(tp.resultType, otherParams) + case _ => + Nil + } + val (typeParams, termParams) = origParams.span(_.isType) + val res = ref(superMeth) + .appliedToTypes(typeParams.map(_.typeRef)) + .appliedToArgss(termParamRefs(constr.info, termParams)) + report.log(i"hoist $arg, cls = $cls = $res") + res + else arg + end hoistSuperArg /** Hoist complex arguments in super call out of the class. */ def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef): Tree = superCall match { diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 77671fc6498c..3cbf5dcfe919 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -62,6 +62,7 @@ object MegaPhase { def prepareForMatch(tree: Match)(using Context): Context = ctx def prepareForCaseDef(tree: CaseDef)(using Context): Context = ctx def prepareForLabeled(tree: Labeled)(using Context): Context = ctx + def prepareForByName(tree: ByName)(using Context): Context = ctx def prepareForReturn(tree: Return)(using Context): Context = ctx def prepareForWhileDo(tree: WhileDo)(using Context): Context = ctx def prepareForTry(tree: Try)(using Context): Context = ctx @@ -96,6 +97,7 @@ object MegaPhase { def transformMatch(tree: Match)(using Context): Tree = tree def transformCaseDef(tree: CaseDef)(using Context): Tree = tree def transformLabeled(tree: Labeled)(using Context): Tree = tree + def transformByName(tree: ByName)(using Context): Tree = tree def transformReturn(tree: Return)(using Context): Tree = tree def transformWhileDo(tree: WhileDo)(using Context): Tree = tree def transformTry(tree: Try)(using Context): Tree = tree @@ -198,6 +200,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { case tree: PackageDef => goPackageDef(tree, start) case tree: Try => goTry(tree, start) case tree: Inlined => goInlined(tree, start) + case tree: ByName => goByName(tree, start) case tree: Return => goReturn(tree, start) case tree: WhileDo => goWhileDo(tree, start) case tree: Alternative => goAlternative(tree, start) @@ -397,6 +400,11 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { val expansion = transformTree(tree.expansion, start)(using inlineContext(tree.call)) goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), start) } + case tree: ByName => + inContext(prepByName(tree, start)(using outerCtx)) { + val expr = transformTree(tree.expr, start) + goByName(cpy.ByName(tree)(expr), start) + } case tree: Return => inContext(prepReturn(tree, start)(using outerCtx)) { val expr = transformTree(tree.expr, start) @@ -535,6 +543,8 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { private val nxCaseDefTransPhase = init("transformCaseDef") private val nxLabeledPrepPhase = init("prepareForLabeled") private val nxLabeledTransPhase = init("transformLabeled") + private val nxByNamePrepPhase = init("prepareForByName") + private val nxByNameTransPhase = init("transformByName") private val nxReturnPrepPhase = init("prepareForReturn") private val nxReturnTransPhase = init("transformReturn") private val nxWhileDoPrepPhase = init("prepareForWhileDo") @@ -812,6 +822,21 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { } } + def prepByName(tree: ByName, start: Int)(using Context): Context = { + val phase = nxByNamePrepPhase(start) + if (phase == null) ctx + else prepByName(tree, phase.idxInGroup + 1)(using phase.prepareForByName(tree)) + } + + def goByName(tree: ByName, start: Int)(using Context): Tree = { + val phase = nxByNameTransPhase(start) + if (phase == null) tree + else phase.transformByName(tree) match { + case tree1: ByName => goByName(tree1, phase.idxInGroup + 1) + case tree1 => transformNode(tree1, phase.idxInGroup + 1) + } + } + def prepReturn(tree: Return, start: Int)(using Context): Context = { val phase = nxReturnPrepPhase(start) if (phase == null) ctx diff --git a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala index e65809fd97ba..44acb9dd1d33 100644 --- a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala +++ b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala @@ -22,7 +22,7 @@ class NonLocalReturns extends MiniPhase { import NonLocalReturns._ import ast.tpd._ - override def runsAfter: Set[String] = Set(ElimByName.name) + override def runsAfter: Set[String] = Set(ByNameLambda.name) private def ensureConforms(tree: Tree, pt: Type)(using Context) = if (tree.tpe <:< pt) tree diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 7546c1fd854d..09904cffc480 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -32,7 +32,7 @@ class PatternMatcher extends MiniPhase { override def transformMatch(tree: Match)(using Context): Tree = if (tree.isInstanceOf[InlineMatch]) tree else { - // Widen termrefs with underlying `=> T` types. Otherwise ElimByName will produce + // Widen termrefs with underlying `=> T` types. Otherwise ByNameLambda will produce // inconsistent types. See i7743.scala. // Question: Does this need to be done more systematically, not just for pattern matches? val matchType = tree.tpe.widenSingleton match diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 5d08921f1ea6..d48b88ef6aa4 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -111,7 +111,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase for case stat: ValDef <- impl.body do val sym = stat.symbol if sym.isAllOf(PrivateParamAccessor, butNot = Mutable) - && !sym.info.isInstanceOf[ExprType] // val-parameters cannot be call-by name, so no need to try to forward to them + && !sym.info.isByName // val-parameters cannot be call-by name, so no need to try to forward to them + // ^^^ drop this restriction? then val idx = superArgs.indexWhere(_.symbol == sym) if idx >= 0 && superParamNames(idx) == stat.name then diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 40c860bf3bdc..54127aa4f708 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -35,8 +35,10 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = override def phaseName: String = ResolveSuper.name - override def runsAfter: Set[String] = Set(ElimByName.name, // verified empirically, need to figure out what the reason is. - PruneErasedDefs.name) // Erased decls make `isCurrent` work incorrectly + override def runsAfter: Set[String] = Set( + ByNameLambda.name, // verified empirically for ElimByName + //^^^ check whether this is also needed for ByNameLambda + PruneErasedDefs.name) // Erased decls make `isCurrent` work incorrectly override def changesMembers: Boolean = true // the phase adds super accessors diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala index 91555dc2b995..06c9f3213eff 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -14,7 +14,7 @@ import scala.collection.mutable class SpecializeFunctions extends MiniPhase { import ast.tpd._ val phaseName = "specializeFunctions" - override def runsAfter = Set(ElimByName.name) + override def runsAfter = Set(ByNameLambda.name) override def isEnabled(using Context): Boolean = !ctx.settings.scalajs.value @@ -76,24 +76,27 @@ class SpecializeFunctions extends MiniPhase { argTypes, retType ) - - if (!isSpecializable || argTypes.exists(_.isInstanceOf[ExprType])) return tree - - val specializedApply = nme.apply.specializedFunction(retType, argTypes) - val newSel = fun match { - case Select(qual, _) => - qual.select(specializedApply) - case _ => - (fun.tpe: @unchecked) match { - case TermRef(prefix: ThisType, name) => - tpd.This(prefix.cls).select(specializedApply) - case TermRef(prefix: NamedType, name) => - tpd.ref(prefix).select(specializedApply) - } - } - - newSel.appliedToTermArgs(args) - + if isSpecializable then + val specializedApply = nme.apply.specializedFunction(retType, argTypes) + val newSel = fun match + case Select(qual, _) => + val qual1 = qual.tpe.widen match + case ByNameType(res) => + // Need to cast to regular function, since specialied apply methods + // are not members of ContextFunction0. The cast will be eliminated in + // erasure. + qual.cast(defn.FunctionOf(Nil, res)) + case _ => + qual + qual1.select(specializedApply) + case _ => + (fun.tpe: @unchecked) match + case TermRef(prefix: ThisType, name) => + tpd.This(prefix.cls).select(specializedApply) + case TermRef(prefix: NamedType, name) => + tpd.ref(prefix).select(specializedApply) + newSel.appliedToTermArgs(args) + else tree case _ => tree } diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 49b6f5564620..12378f6e1d8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -147,6 +147,7 @@ object Splicer { def checkIfValidArgument(tree: Tree)(using Env): Unit = tree match { case Block(Nil, expr) => checkIfValidArgument(expr) case Typed(expr, _) => checkIfValidArgument(expr) + case ByName(expr) => checkIfValidArgument(expr) case Apply(Select(Apply(fn, quoted :: Nil), nme.apply), _) if fn.symbol == defn.QuotedRuntime_exprQuote => val noSpliceChecker = new TreeTraverser { @@ -291,6 +292,7 @@ object Splicer { // `val j$1 = x; val i$1 = y; foo(i = i$1, j = j$1)` case Block(stats, expr) => interpretBlock(stats, expr) case NamedArg(_, arg) => interpretTree(arg) + case ByName(arg) => () => interpretTree(arg) case Inlined(_, bindings, expansion) => interpretBlock(bindings, expansion) @@ -305,24 +307,15 @@ object Splicer { } private def interpretArgs(argss: List[List[Tree]], fnType: Type)(using Env): List[Object] = { - def interpretArgsGroup(args: List[Tree], argTypes: List[Type]): List[Object] = - assert(args.size == argTypes.size) - val view = - for (arg, info) <- args.lazyZip(argTypes) yield - info match - case _: ExprType => () => interpretTree(arg) // by-name argument - case _ => interpretTree(arg) // by-value argument - view.toList - fnType.dealias match case fnType: MethodType if fnType.isErasedMethod => interpretArgs(argss, fnType.resType) case fnType: MethodType => val argTypes = fnType.paramInfos assert(argss.head.size == argTypes.size) - interpretArgsGroup(argss.head, argTypes) ::: interpretArgs(argss.tail, fnType.resType) + argss.head.map(interpretTree) ::: interpretArgs(argss.tail, fnType.resType) case fnType: AppliedType if defn.isContextFunctionType(fnType) => val argTypes :+ resType = fnType.args - interpretArgsGroup(argss.head, argTypes) ::: interpretArgs(argss.tail, resType) + argss.head.map(interpretTree) ::: interpretArgs(argss.tail, resType) case fnType: PolyType => interpretArgs(argss, fnType.resType) case fnType: ExprType => interpretArgs(argss, fnType.resType) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala deleted file mode 100644 index 9eb9b3f0ad5a..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala +++ /dev/null @@ -1,67 +0,0 @@ -package dotty.tools -package dotc -package transform - -import MegaPhase._ -import core._ -import Symbols._ -import SymDenotations._ -import Contexts._ -import Types._ -import Flags._ -import Decorators._ -import DenotTransformers._ -import StdNames.nme -import NameKinds.SuperArgName -import ast.Trees._ -import reporting.trace - -/** Abstract base class of ByNameClosures and ElimByName, factoring out the - * common functionality to transform arguments of by-name parameters. - */ -abstract class TransformByNameApply extends MiniPhase { thisPhase: DenotTransformer => - import ast.tpd._ - - /** The info of the tree's symbol before it is potentially transformed in this phase */ - private def originalDenotation(tree: Tree)(using Context) = - atPhase(thisPhase)(tree.symbol.denot) - - /** If denotation had an ExprType before, it now gets a function type */ - protected def exprBecomesFunction(symd: SymDenotation)(using Context): Boolean = - symd.is(Param) || symd.is(ParamAccessor, butNot = Method) - - protected def isByNameRef(tree: Tree)(using Context): Boolean = { - val origDenot = originalDenotation(tree) - origDenot.info.isInstanceOf[ExprType] && exprBecomesFunction(origDenot) - } - - def mkByNameClosure(arg: Tree, argType: Type)(using Context): Tree = unsupported(i"mkClosure($arg)") - - override def transformApply(tree: Apply)(using Context): Tree = - trace(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { - - def transformArg(arg: Tree, formal: Type): Tree = formal.dealias match { - case formalExpr: ExprType => - var argType = arg.tpe.widenIfUnstable - if (argType.isBottomType) argType = formal.widenExpr - def wrap(arg: Tree) = - ref(defn.cbnArg).appliedToType(argType).appliedTo(arg).withSpan(arg.span) - def unTyped(t: Tree): Tree = t match { case Typed(expr, _) => unTyped(expr) case _ => t } - unTyped(arg) match { - case Apply(Select(qual, nme.apply), Nil) - if qual.tpe.derivesFrom(defn.Function0) && (isPureExpr(qual) || qual.symbol.isAllOf(Inline | Param)) => - wrap(qual) - case _ => - if isByNameRef(arg) || arg.symbol == defn.cbnArg || arg.symbol.name.is(SuperArgName) - then arg - else wrap(mkByNameClosure(arg, argType)) - } - case _ => - arg - } - - val mt @ MethodType(_) = tree.fun.tpe.widen - val args1 = tree.args.zipWithConserve(mt.paramInfos)(transformArg) - cpy.Apply(tree)(tree.fun, args1) - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 29fd1adb6688..4ec6f8ae43c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -508,6 +508,14 @@ class TreeChecker extends Phase with SymTransformer { tpdTree } + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = + val sym = vdef.symbol + if sym.is(Param) || sym.is(ParamAccessor, butNot = Method) then + assert(!sym.info.isInstanceOf[ExprType], + i"""Parameter $sym of ${sym.owner} should not have ExprType ${sym.info} + |Definition = $vdef""") + super.typedValDef(vdef, sym) + override def typedCase(tree: untpd.CaseDef, sel: Tree, selType: Type, pt: Type)(using Context): CaseDef = withPatSyms(tpd.patVars(tree.pat.asInstanceOf[tpd.Tree])) { super.typedCase(tree, sel, selType, pt) @@ -596,7 +604,8 @@ class TreeChecker extends Phase with SymTransformer { i"""|${mismatch.msg} |found: ${infoStr(tree.tpe)} |expected: ${infoStr(pt)} - |tree = $tree""".stripMargin + |tree = $tree + |trace = ${mismatch.explain}""" }) tree } diff --git a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala index 7a3da6ad4bde..3fde83d73093 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -34,9 +34,6 @@ object TypeUtils { self.derivesFrom(defn.ExceptionClass) && !self.derivesFrom(defn.RuntimeExceptionClass) - def isByName: Boolean = - self.isInstanceOf[ExprType] - def ensureMethodic(using Context): Type = self match { case self: MethodicType => self case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 88794c609805..23a13d38132f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -1192,6 +1192,9 @@ object Semantic { case Return(expr, from) => eval(expr, thisV, klass).ensureHot("return expression may only be initialized value", expr) + case ByName(expr) => + eval(expr, thisV, klass) + case WhileDo(cond, body) => val ress = eval(cond :: body :: Nil, thisV, klass) Result(Hot, ress.flatMap(_.errors)) @@ -1491,7 +1494,7 @@ object Semantic { case hklambda: HKTypeLambda => typeRefOf(hklambda.resType) } - opaque type Arg = Tree | ByNameArg + opaque type Arg = Tree | ByNameArg // ^^^ can be simplified case class ByNameArg(tree: Tree) extension (arg: Arg) @@ -1502,14 +1505,16 @@ object Semantic { object Call { + private def isDelayed(tp: Type)(using Context) = + tp.isInstanceOf[MethodicType] || tp.isByName + def unapply(tree: Tree)(using Context): Option[(Tree, List[List[Arg]])] = tree match case Apply(fn, args) => val argTps = fn.tpe.widen match case mt: MethodType => mt.paramInfos - val normArgs: List[Arg] = args.zip(argTps).map { - case (arg, _: ExprType) => ByNameArg(arg) - case (arg, _) => arg + val normArgs: List[Arg] = args.zip(argTps).map { (arg, formal) => + if formal.isByName then ByNameArg(arg.dropByName) else arg } unapply(fn) match case Some((ref, args0)) => Some((ref, args0 :+ normArgs)) @@ -1518,7 +1523,7 @@ object Semantic { case TypeApply(fn, targs) => unapply(fn) - case ref: RefTree if ref.tpe.widenSingleton.isInstanceOf[MethodicType] => + case ref: RefTree if isDelayed(ref.tpe.widenSingleton) => Some((ref, Nil)) case _ => None diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index d8e9c413decb..dbfc832f74b4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -356,7 +356,7 @@ trait Applications extends Compatibility { /** If constructing trees, turn last `n` processed arguments into a * `SeqLiteral` tree with element type `elemFormal`. */ - protected def makeVarArg(n: Int, elemFormal: Type): Unit + protected def makeVarArg(n: Int, elemFormal: Type, formal: Type): Unit /** If all `args` have primitive numeric types, make sure it's the same one */ protected def harmonizeArgs(args: List[TypedArg]): List[TypedArg] @@ -591,7 +591,7 @@ trait Applications extends Compatibility { case (arg @ Typed(Literal(Constant(null)), _)) :: Nil if ctx.isAfterTyper => addTyped(arg) case _ => - val elemFormal = formal.widenExpr.argTypesLo.head + val elemFormal = formal.widenByName.argTypesLo.head val typedArgs = harmonic(harmonizeArgs, elemFormal) { args.map { arg => @@ -600,7 +600,7 @@ trait Applications extends Compatibility { } } typedArgs.foreach(addArg(_, elemFormal)) - makeVarArg(args.length, elemFormal) + makeVarArg(args.length, elemFormal, formal) } else args match { case EmptyTree :: args1 => @@ -678,7 +678,7 @@ trait Applications extends Compatibility { && { val argtpe1 = argtpe.widen val captured = captureWildcards(argtpe1) - (captured ne argtpe1) && isCompatible(captured, formal.widenExpr) + (captured ne argtpe1) && isCompatible(captured, formal.widenByName) } /** The type of the given argument */ @@ -686,7 +686,7 @@ trait Applications extends Compatibility { def typedArg(arg: Arg, formal: Type): Arg = arg final def addArg(arg: TypedArg, formal: Type): Unit = ok = ok & argOK(arg, formal) - def makeVarArg(n: Int, elemFormal: Type): Unit = {} + def makeVarArg(n: Int, elemFormal: Type, formal: Type): Unit = {} def fail(msg: Message, arg: Arg): Unit = ok = false def fail(msg: Message): Unit = @@ -737,11 +737,11 @@ trait Applications extends Compatibility { def addArg(arg: Tree, formal: Type): Unit = typedArgBuf += adapt(arg, formal.widenExpr) - def makeVarArg(n: Int, elemFormal: Type): Unit = { + def makeVarArg(n: Int, elemFormal: Type, formal: Type): Unit = { val args = typedArgBuf.takeRight(n).toList typedArgBuf.dropRightInPlace(n) val elemtpt = TypeTree(elemFormal) - typedArgBuf += seqToRepeated(SeqLiteral(args, elemtpt)) + typedArgBuf += seqToRepeated(SeqLiteral(args, elemtpt)).alignByName(formal) } def harmonizeArgs(args: List[TypedArg]): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 85eba072f7a8..2096e67aaa34 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -678,7 +678,7 @@ object Checking { case param :: params => if (param.is(Mutable)) report.error(ValueClassParameterMayNotBeAVar(clazz, param), param.srcPos) - if (param.info.isInstanceOf[ExprType]) + if (param.info.isByName) // ^^^ drop? report.error(ValueClassParameterMayNotBeCallByName(clazz, param), param.srcPos) if (param.is(Erased)) report.error("value class first parameter cannot be `erased`", param.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index c04b5f1d2d85..f62c88764651 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -32,31 +32,28 @@ abstract class Lifter { def noLift(expr: Tree)(using Context): Boolean /** The corresponding lifter for pass-by-name arguments */ - protected def exprLifter: Lifter = NoLift - - /** The flags of a lifted definition */ - protected def liftedFlags: FlagSet = EmptyFlags - - /** The tree of a lifted definition */ - protected def liftedDef(sym: TermSymbol, rhs: Tree)(using Context): MemberDef = ValDef(sym, rhs) + protected def byNameLifter: Lifter = NoLift /** Is lifting performed on erased terms? */ protected def isErased = false private def lift(defs: mutable.ListBuffer[Tree], expr: Tree, prefix: TermName = EmptyTermName)(using Context): Tree = - if (noLift(expr)) expr - else { + if noLift(expr) then + expr + else val name = UniqueName.fresh(prefix) - // don't instantiate here, as the type params could be further constrained, see tests/pos/pickleinf.scala - var liftedType = expr.tpe.widen.deskolemized - if (liftedFlags.is(Method)) liftedType = ExprType(liftedType) - val lifted = newSymbol(ctx.owner, name, liftedFlags | Synthetic, liftedType, coord = spanCoord(expr.span)) - defs += liftedDef(lifted, expr) + val (liftedFlags, rhs, mkDef, wrapType, wrapRef) = expr match + case ByName(body) => + (Method, body, DefDef(_: TermSymbol, body), ExprType(_: Type), ByName(_: Tree)) + case _ => + (EmptyFlags, expr, ValDef(_: TermSymbol, expr), identity(_: Type), identity(_: Tree)) + val lifted = newSymbol( + ctx.owner, name, liftedFlags | Synthetic, wrapType(rhs.tpe.widen.deskolemized), coord = spanCoord(expr.span)) + defs += mkDef(lifted) .withSpan(expr.span) .changeNonLocalOwners(lifted) .setDefTree - ref(lifted.termRef).withSpan(expr.span.focus) - } + wrapRef(ref(lifted.termRef).withSpan(expr.span.focus)) /** Lift out common part of lhs tree taking part in an operator assignment such as * @@ -90,7 +87,7 @@ abstract class Lifter { args.lazyZip(mt.paramNames).lazyZip(mt.paramInfos).map { (arg, name, tp) => if tp.hasAnnotation(defn.InlineParamAnnot) then arg else - val lifter = if (tp.isInstanceOf[ExprType]) exprLifter else this + val lifter = if tp.isByName then byNameLifter else this lifter.liftArg(defs, arg, if (name.firstPart contains '$') EmptyTermName else name) } case _ => @@ -153,19 +150,13 @@ object LiftImpure extends LiftImpure /** Lift all impure or complex arguments */ class LiftComplex extends Lifter { def noLift(expr: tpd.Tree)(using Context): Boolean = tpd.isPurePath(expr) - override def exprLifter: Lifter = LiftToDefs + override def byNameLifter: Lifter = this } object LiftComplex extends LiftComplex object LiftErased extends LiftComplex: override def isErased = true -/** Lift all impure or complex arguments to `def`s */ -object LiftToDefs extends LiftComplex { - override def liftedFlags: FlagSet = Method - override def liftedDef(sym: TermSymbol, rhs: tpd.Tree)(using Context): tpd.DefDef = tpd.DefDef(sym, rhs) -} - /** Lifter for eta expansion */ object EtaExpansion extends LiftImpure { import tpd._ @@ -209,19 +200,6 @@ object EtaExpansion extends LiftImpure { * In each case, the result is an untyped tree, with `es` and `expr` as typed splices. * * F[V](x) ==> (x => F[X]) - * - * Note: We allow eta expanding a method with a call by name parameter like - * - * def m(x: => T): T - * - * to a value of type (=> T) => T. This type cannot be written in source, since - * by-name types => T are not legal argument types. - * - * It would be simpler to not allow to eta expand by-name methods. That was the rule - * initially, but at some point, the rule was dropped. Enforcing the restriction again - * now would break existing code. Allowing by-name parameters in function types seems to - * be OK. After elimByName they are all converted to regular function types anyway. - * But see comment on the `ExprType` case in function `prune` in class `ConstraintHandling`. */ def etaExpand(tree: Tree, mt: MethodType, xarity: Int)(using Context): untpd.Tree = { import untpd._ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 4824031f12bc..975141e171e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -134,7 +134,7 @@ object Implicits: else if (mt.paramInfos.lengthCompare(1) == 0 && { var formal = widenSingleton(mt.paramInfos.head) if (approx) formal = wildApprox(formal) - explore(argType relaxed_<:< formal.widenExpr) + explore(argType relaxed_<:< formal.widenDelayed) }) Candidate.Conversion else @@ -813,7 +813,7 @@ trait Implicits: if !ctx.mode.is(Mode.ImplicitsEnabled) || from.isInstanceOf[Super] then NoMatchingImplicitsFailure else { - def adjust(to: Type) = to.stripTypeVar.widenExpr match { + def adjust(to: Type) = to.stripTypeVar.widenDelayed match { case SelectionProto(name, memberProto, compat, true) => SelectionProto(name, memberProto, compat, privateOK = false) case tp => tp @@ -1038,7 +1038,7 @@ trait Implicits: result0 } // If we are at the outermost implicit search then emit the implicit dictionary, if any. - ctx.searchHistory.emitDictionary(span, result) + ctx.searchHistory.emitDictionary(span, result, pt) } /** Try to typecheck an implicit reference */ @@ -1051,12 +1051,12 @@ trait Implicits: val locked = ctx.typerState.ownedVars val adapted = if argument.isEmpty then - if defn.isContextFunctionType(pt) then + if defn.isContextFunctionType(pt) && !pt.isByName then // need to go through typed, to build the context closure typed(untpd.TypedSplice(generated), pt, locked) else // otherwise we can skip typing and go directly to adapt - adapt(generated, pt.widenExpr, locked) + adapt(generated, pt.widenDelayed, locked).alignByName(pt) else { def untpdGenerated = untpd.TypedSplice(generated) def producesConversion(info: Type): Boolean = info match @@ -1128,7 +1128,7 @@ trait Implicits: /** An implicit search; parameters as in `inferImplicit` */ class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context): - assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], + assert(argument.isEmpty || argument.tpe.isValueType, em"found: $argument: ${argument.tpe}, expected: $pt") private def nestedContext() = @@ -1136,7 +1136,7 @@ trait Implicits: private def isCoherent = pt.isRef(defn.CanEqualClass) - val wideProto = pt.widenExpr + val wideProto = pt.widenDelayed /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ val wildProto: Type = @@ -1550,7 +1550,7 @@ trait Implicits: history match case prev @ OpenSearch(cand1, tp, outer) => if cand1.ref eq cand.ref then - lazy val wildTp = wildApprox(tp.widenExpr) + lazy val wildTp = wildApprox(tp.widenDelayed) if belowByname && (wildTp <:< wildPt) then fullyDefinedType(tp, "by-name implicit parameter", span) false @@ -1563,7 +1563,9 @@ trait Implicits: else loop(outer, tp.isByName || belowByname) case _ => false - loop(ctx.searchHistory, pt.isByName) + val res = loop(ctx.searchHistory, pt.isByName) + if res then implicits.println(i"divergence detected for ${cand.ref}") + res end checkDivergence /** @@ -1598,13 +1600,13 @@ trait Implicits: def loop(history: SearchHistory, belowByname: Boolean): Type = history match case OpenSearch(cand, tp, outer) => - if (belowByname || tp.isByName) && tp.widenExpr <:< wideProto then tp + if (belowByname || tp.isByName) && tp.widenDelayed <:< wideProto then tp else loop(outer, belowByname || tp.isByName) case _ => NoType loop(ctx.searchHistory, pt.isByName) match case NoType => NoType - case tp => ctx.searchHistory.linkBynameImplicit(tp.widenExpr) + case tp => ctx.searchHistory.linkBynameImplicit(tp.widenDelayed) end recursiveRef end ImplicitSearch end Implicits @@ -1645,7 +1647,7 @@ abstract class SearchHistory: root.defineBynameImplicit(tpe, result) // This is NOOP unless at the root of this search history. - def emitDictionary(span: Span, result: SearchResult)(using Context): SearchResult = result + def emitDictionary(span: Span, result: SearchResult, pt: Type)(using Context): SearchResult = result override def toString: String = s"SearchHistory(open = $openSearchPairs, byname = $byname)" end SearchHistory @@ -1732,8 +1734,11 @@ final class SearchRoot extends SearchHistory: override def defineBynameImplicit(tpe: Type, result: SearchSuccess)(using Context): SearchResult = implicitDictionary.get(tpe) match { case Some((ref, _)) => - implicitDictionary.put(tpe, (ref, result.tree)) - SearchSuccess(tpd.ref(ref).withSpan(result.tree.span), result.ref, result.level)(result.tstate, result.gstate) + import tpd.TreeOps + val rhs = result.tree.dropByName + implicitDictionary.put(tpe, (ref, rhs)) + val arg = tpd.ref(ref).withSpan(rhs.span) + SearchSuccess(arg, result.ref, result.level)(result.tstate, result.gstate) case None => result } @@ -1745,7 +1750,7 @@ final class SearchRoot extends SearchHistory: * @result The elaborated result, comprising the implicit dictionary and a result tree * substituted with references into the dictionary. */ - override def emitDictionary(span: Span, result: SearchResult)(using Context): SearchResult = + override def emitDictionary(span: Span, result: SearchResult, pt: Type)(using Context): SearchResult = if (myImplicitDictionary == null || implicitDictionary.isEmpty) result else result match { @@ -1805,13 +1810,26 @@ final class SearchRoot extends SearchHistory: val vsymMap = (vsyms zip nsyms).toMap val rhss = pruned.map(_._2) + // Substitute dictionary references into dictionary entry RHSs val rhsMap = new TreeTypeMap(treeMap = { case id: Ident if vsymMap.contains(id.symbol) => tpd.ref(vsymMap(id.symbol))(using ctx.withSource(id.source)).withSpan(id.span) case tree => tree }) - val nrhss = rhss.map(rhsMap(_)) + + // Add by-name closures in arguments if corresponding formal is a by-name parameter. + val alignByNameInArgs = new TreeMap: + override def transform(tree: Tree)(using Context) = super.transform(tree) match + case app @ Apply(fn, args) => + fn.tpe.widen match + case mt: MethodType => + import tpd.TreeOps + tpd.cpy.Apply(app)(fn, args.zipWithConserve(mt.paramInfos)(_.alignByName(_))) + case _ => app + case tree => tree + + val nrhss = rhss.map(rhsMap(_)).map(alignByNameInArgs.transform(_)) val vdefs = (nsyms zip nrhss) map { case (nsym, nrhs) => ValDef(nsym.asTerm, nrhs.changeNonLocalOwners(nsym)) @@ -1830,9 +1848,10 @@ final class SearchRoot extends SearchHistory: case tree => tree }) - val res = resMap(success.tree) + val res = alignByNameInArgs.transform(resMap(success.tree)) val blk = Block(classDef :: inst :: Nil, res).withSpan(span) + .alignByName(pt) success.copy(tree = blk)(success.tstate, success.gstate) } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 722e3abfec85..3912da1cb9c6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -478,12 +478,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { */ private def paramBindingDef(name: Name, formal: Type, arg0: Tree, buf: DefBuffer)(using Context): ValOrDefDef = { - val isByName = formal.dealias.isInstanceOf[ExprType] - val arg = arg0 match { + val isByName = formal.isByName + + def normArg(arg: Tree): Tree = arg match case Typed(arg1, tpt) if tpt.tpe.isRepeatedParam && arg1.tpe.derivesFrom(defn.ArrayClass) => wrapArray(arg1, arg0.tpe.elemType) - case _ => arg0 - } + case ByName(arg1) => + assert(isByName) + normArg(arg1) + case _ => arg + + val arg = normArg(arg0) val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false) val argIsBottom = argtpe.isBottomTypeAfterErasure val bindingType = @@ -491,9 +496,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { else if isByName then ExprType(argtpe.widen) else argtpe.widen var bindingFlags: FlagSet = InlineProxy - if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then + if formal.widenByName.hasAnnotation(defn.InlineParamAnnot) then bindingFlags |= Inline - if formal.widenExpr.hasAnnotation(defn.ErasedParamAnnot) then + if formal.widenByName.hasAnnotation(defn.ErasedParamAnnot) then bindingFlags |= Erased if isByName then bindingFlags |= Method @@ -932,6 +937,40 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { else super.transformInlined(tree) end InlinerMap + def transformInlinedTree(tree: 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 Apply(Select(fn: Ident, nme.apply), Nil) + if fn.tpe.widen.isByName && paramProxy.contains(fn.tpe) => + // param proxies are parameterless defs, so drop the `.apply()` + transformInlinedTree(fn) + 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(TypeTree(t).withSpan(argSpan))(tree.span) + case _ => tree + } + case tree => tree + end transformInlinedTree + // 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 @@ -950,35 +989,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { 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(TypeTree(t).withSpan(argSpan))(tree.span) - case _ => tree - } - case tree => tree - }, + treeMap = transformInlinedTree, oldOwners = inlinedMethod :: Nil, newOwners = ctx.owner :: Nil, substFrom = Nil, @@ -1543,7 +1554,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { inlineIfNeeded(tryInlineArg(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt)) override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { - val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) + val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, tree.typeOpt, this)) val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) val reducedProjection = reducer.reduceProjection(resNoReduce) if reducedProjection.isType then diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 4bca5c50cf77..3e8efe022ad2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1534,7 +1534,12 @@ class Namer { typer: Typer => case _ => WildcardType } - val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) + var mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) + mbrTpe match + case ByNameType(underlying) if sym.isAllOf(InlineParam) => + report.deprecationWarning(i"inline by name parameters are deprecated; use simple inline parameters instead", mdef.srcPos) + mbrTpe = underlying + case _ => if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue)) else mbrTpe @@ -1651,7 +1656,7 @@ class Namer { typer: Typer => ctx.defContext(sym).denotNamed(original) def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match { case params :: paramss1 => - if (idx < params.length) params(idx) + if (idx < params.length) params(idx).widenByName else paramProto(paramss1, idx - params.length) case nil => NoType diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 0ea7207c7b2b..1083864ee6c7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -498,11 +498,20 @@ object Nullables: * keep it. Otherwise issue an error that the call-by-name argument was typed using * flow assumptions about mutable variables and suggest that it is enclosed * in a `byName(...)` call instead. + * + * This step is needed because arguments might be evaluated in calls to overloaded + * methods that have both cbn and cbv parameters, so we do not know yet whether an + * argument is cbn. We always typecheck such arguments under the assumption + * that they are cbv, and we have to correct this assumption if the actual resolved + * method is cbn. If the call is non-overloaded, we do the right thing from the start. + * Inserting a byName call just makes clear that the argumnent is cbn. There is no + * special treatment in the compiler associated with that method; it is just the + * cbn identity. */ def postProcessByNameArgs(fn: TermRef, app: Tree)(using Context): Tree = fn.widen match case mt: MethodType - if mt.paramInfos.exists(_.isInstanceOf[ExprType]) && !fn.symbol.is(Inline) => + if mt.paramInfos.exists(_.isByName) && !fn.symbol.is(Inline) => app match case Apply(fn, args) => object dropNotNull extends TreeMap: @@ -547,8 +556,8 @@ object Nullables: def recur(formals: List[Type], args: List[Tree]): List[Tree] = (formals, args) match case (formal :: formalsRest, arg :: argsRest) => val arg1 = - if formal.isInstanceOf[ExprType] - then postProcess(formal.widenExpr.repeatedToSingle, arg) + if formal.isByName + then postProcess(formal.widenByName.repeatedToSingle, arg) else arg val argsRest1 = recur( if formal.isRepeatedParam then formals else formalsRest, @@ -557,7 +566,7 @@ object Nullables: else arg1 :: argsRest1 case _ => args - tpd.cpy.Apply(app)(fn, recur(mt.paramInfos, args)) + tpd.cpy.Apply(app)(fn, recur(mt.paramInfos, args)) // ^^^ can do without paramInfos since arguments are now marked case _ => app case _ => app end postProcessByNameArgs diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 76fcb77d8c30..7fdcb47a214e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -36,7 +36,7 @@ object ProtoTypes { * If `pt` is a by-name type, we compare against the underlying type instead. */ def isCompatible(tp: Type, pt: Type)(using Context): Boolean = - (tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt) + (tp.widenDelayed relaxed_<:< pt.widenDelayed) || viewExists(tp, pt) /** Like normalize and then isCompatible, but using a subtype comparison with * necessary eithers that does not unnecessarily truncate the constraint space, @@ -79,7 +79,7 @@ object ProtoTypes { */ def constrainResult(mt: Type, pt: Type)(using Context): Boolean = val savedConstraint = ctx.typerState.constraint - val res = pt.widenExpr match { + val res = pt.widenDelayed match { case pt: FunProto => mt match case mt: MethodType => @@ -93,7 +93,7 @@ object ProtoTypes { else true } case _ => true - case _: ValueTypeOrProto if !disregardProto(pt) => + case pt: ValueTypeOrProto if !disregardProto(pt) => necessarilyCompatible(mt, pt) case pt: WildcardType if pt.optBounds.exists => necessarilyCompatible(mt, pt) @@ -422,15 +422,14 @@ object ProtoTypes { * used to avoid repeated typings of trees when backtracking. */ def typedArg(arg: untpd.Tree, formal: Type)(using Context): Tree = { - val wideFormal = formal.widenExpr val argCtx = - if wideFormal eq formal then ctx - else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables) + if formal.isByName then ctx.withNotNullInfos(ctx.notNullInfos.retractMutables) + else ctx val locked = ctx.typerState.ownedVars val targ = cacheTypedArg(arg, - typer.typedUnadapted(_, wideFormal, locked)(using argCtx), + typer.typedUnadapted(_, formal, locked)(using argCtx), force = true) - typer.adapt(targ, wideFormal, locked) + typer.adapt(targ, formal, locked) } /** The type of the argument `arg`, or `NoType` if `arg` has not been typed before diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 86cd7e5f24f3..6bcfb0b2885c 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -251,7 +251,7 @@ trait QuotesAndSplices { val freshTypeBindingsBuff = new mutable.ListBuffer[Tree] val typePatBuf = new mutable.ListBuffer[Tree] override def transform(tree: Tree)(using Context) = tree match { - case Typed(Apply(fn, pat :: Nil), tpt) if fn.symbol.isExprSplice && !tpt.tpe.derivesFrom(defn.RepeatedParamClass) => + case Typed(Apply(fn, pat :: Nil), tpt) if fn.symbol.isExprSplice && !tpt.tpe.isRepeatedParam => val tpt1 = transform(tpt) // Transform type bindings val exprTpt = AppliedTypeTree(TypeTree(defn.QuotedExprClass.typeRef), tpt1 :: Nil) val newSplice = ref(defn.QuotedRuntime_exprSplice).appliedToType(tpt1.tpe).appliedTo(Typed(pat, exprTpt)) diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 85be8c32227a..78f9d2c0863f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -130,6 +130,9 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def inferView(from: Tree, to: Type)(using Context): Implicits.SearchResult = Implicits.NoMatchingImplicitsFailure + + override def needsForcing(tree: Tree, wtp: Type, pt: Type)(using Context): Boolean = false + override def checkCanEqual(ltp: Type, rtp: Type, span: Span)(using Context): Unit = () override def widenEnumCase(tree: Tree, pt: Type)(using Context): Tree = tree diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index fe4feecd6100..1967322a7509 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -498,15 +498,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): tp.derivedRefinedType(baseWithRefinements(parent), rname, rinfo) case _ => tp.baseType(cls) - val base = baseWithRefinements(formal) + val wformal = formal.widenByName + val base = baseWithRefinements(wformal) val result = - if (base <:< formal.widenExpr) + if (base <:< wformal) // With the subtype test we enforce that the searched type `formal` is of the right form handler(base, span) else EmptyTree result.orElse(recur(rest)) case Nil => EmptyTree - recur(specialHandlers) + val res = recur(specialHandlers) + if res.isEmpty then res else res.alignByName(formal) end Synthesizer diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 3dcec413540f..cfab42fc4525 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -425,6 +425,9 @@ trait TypeAssigner { def assignType(tree: untpd.Labeled)(using Context): Labeled = tree.withType(tree.bind.symbol.info) + def assignType(tree: untpd.ByName, expr: Tree)(using Context): ByName = + tree.withType(ByNameType(expr.tpe)) + def assignType(tree: untpd.Return)(using Context): Return = tree.withType(defn.NothingType) @@ -482,7 +485,7 @@ trait TypeAssigner { } def assignType(tree: untpd.ByNameTypeTree, result: Tree)(using Context): ByNameTypeTree = - tree.withType(ExprType(result.tpe)) + tree.withType(ByNameType(result.tpe)) def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree, alias: Tree)(using Context): TypeBoundsTree = tree.withType( diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 957a51532886..6d91e0a83a19 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1171,7 +1171,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => mapOver(t) } - val pt1 = pt.stripTypeVar.dealias.normalized + val pt1 = pt.widenByName.stripTypeVar.dealias.normalized if (pt1 ne pt1.dropDependentRefinement) && defn.isContextFunctionType(pt1.nonPrivateMember(nme.apply).info.finalResultType) then @@ -1362,7 +1362,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer lazy val calleeType: Type = untpd.stripAnnotated(untpd.unsplice(fnBody)) match { case ident: untpd.Ident if isContextual => val ident1 = typedIdent(ident, WildcardType) - val tp = ident1.tpe.widen + val tp = ident1.tpe.widenByName.widen if defn.isContextFunctionType(tp) && params.size == defn.functionArity(tp) then paramIndex = params.map(_.name).zipWithIndex.toMap fnBody = untpd.TypedSplice(ident1) @@ -1418,17 +1418,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer defn.isProductSubType(formal) && tupleComponentTypes(formal).corresponds(params) { (argType, param) => - param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe + param.tpt.isEmpty || argType.widenDelayed <:< typedAheadType(param.tpt).tpe } - val desugared = - if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) { + val desugared = protoFormals match + case pformal :: Nil if params.length != 1 && ptIsCorrectProduct(pformal.widenByName) => val isGenericTuple = protoFormals.head.derivesFrom(defn.TupleClass) && !defn.isTupleClass(protoFormals.head.typeSymbol) desugar.makeTupledFunction(params, fnBody, isGenericTuple) - } - else { + case _ => val inferredParams: List[untpd.ValDef] = for ((param, i) <- params.zipWithIndex) yield if (!param.tpt.isEmpty) param @@ -1446,7 +1445,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer ) cpy.ValDef(param)(tpt = paramTpt) desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual, tree.span) - } + typed(desugared, pt) } @@ -1715,6 +1714,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer caseRest(using ctx.fresh.setFreshGADTBounds.setNewScope) } + def typedByName(tree: untpd.ByName, pt: Type)(using Context): ByName = + val expr1 = typed(tree.expr, pt.widenByName) + assignType(cpy.ByName(tree)(expr1), expr1) + def typedReturn(tree: untpd.Return)(using Context): Return = def enclMethInfo(cx: Context): (Tree, Type) = @@ -2263,7 +2266,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer params <- paramss1.dropWhile(TypeDefs.unapply(_).isDefined).take(1) case param: ValDef <- params do - if defn.isContextFunctionType(param.tpt.tpe) then + if defn.isContextFunctionType(param.tpt.tpe) && !param.tpt.tpe.isByName then report.error("case class element cannot be a context function", param.srcPos) else if sym.targetName != sym.name then @@ -2814,6 +2817,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.Import => typedImport(tree, retrieveSym(tree)) case tree: untpd.Export => typedExport(tree) case tree: untpd.Match => typedMatch(tree, pt) + case tree: untpd.ByName => typedByName(tree, pt) case tree: untpd.Return => typedReturn(tree) case tree: untpd.WhileDo => typedWhileDo(tree) case tree: untpd.Try => typedTry(tree, pt) @@ -2859,10 +2863,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && !ctx.isAfterTyper && !ctx.isInlineContext then - makeContextualFunction(xtree, ifpt) + makeContextualFunction(xtree, ifpt, locked) else xtree match - case xtree: untpd.NameTree => typedNamed(xtree, pt) - case xtree => typedUnnamed(xtree) + case xtree: untpd.NameTree => typedNamed(xtree, pt) + case xtree => typedUnnamed(xtree) simplify(result, pt, locked) catch case ex: TypeError => errorTree(xtree, ex, xtree.srcPos.focus) @@ -2882,8 +2886,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree.overwriteType(tree.tpe.simplified) tree - protected def makeContextualFunction(tree: untpd.Tree, pt: Type)(using Context): Tree = { - val defn.FunctionOf(formals, _, true, _) = pt.dropDependentRefinement + protected def makeContextualFunction(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = { + val defn.FunctionOf(formals, resType, true, _) = pt.dropDependentRefinement // The getter of default parameters may reach here. // Given the code below @@ -2905,15 +2909,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // // see tests/pos/i7778b.scala - val paramTypes = { - val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], StopAt.Static)) - if hasWildcard then formals.map(_ => untpd.TypeTree()) - else formals.map(untpd.TypeTree) - } - - val ifun = desugar.makeContextualFunction(paramTypes, tree, defn.isErasedFunctionType(pt)) - typr.println(i"make contextual function $tree / $pt ---> $ifun") - typedFunctionValue(ifun, pt) + if formals.isEmpty then // expected type is a by-name type + ByName(typedUnadapted(tree, resType)) + else + val paramTypes = + val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], StopAt.Static)) + if hasWildcard then formals.map(_ => untpd.TypeTree()) + else formals.map(untpd.TypeTree) + + val ifun = desugar.makeContextualFunction(paramTypes, tree, defn.isErasedFunctionType(pt)) + typr.println(i"make contextual function $tree / $pt ---> $ifun") + typedFunctionValue(ifun, pt) // TODO: thread locked through this } /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ @@ -3606,8 +3612,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def adaptNoArgsOther(wtp: Type, functionExpected: Boolean): Tree = { - val implicitFun = isContextFunctionRef(wtp) && !untpd.isContextualClosure(tree) - def caseCompanion = + val isContextFunRef = isContextFunctionRef(wtp) && !untpd.isContextualClosure(tree) + def isCaseCompanion = functionExpected && tree.symbol.is(Module) && tree.symbol.companionClass.is(Case) && @@ -3616,20 +3622,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer true } - if ((implicitFun || caseCompanion) && - !isApplyProto(pt) && - pt != AssignProto && - !ctx.mode.is(Mode.Pattern) && - !ctx.isAfterTyper && - !ctx.isInlineContext) { + if (isContextFunRef || isCaseCompanion) + && !wtp.isByName + && !isApplyProto(pt) + && pt != AssignProto + && !ctx.mode.is(Mode.Pattern) + && !ctx.isAfterTyper + && !ctx.isInlineContext + then typr.println(i"insert apply on implicit $tree") val sel = untpd.Select(untpd.TypedSplice(tree), nme.apply).withAttachment(InsertedApply, ()) try typed(sel, pt, locked) finally sel.removeAttachment(InsertedApply) - } - else if (ctx.mode is Mode.Pattern) { + else if (ctx.mode is Mode.Pattern) checkEqualityEvidence(tree, pt) tree - } else val meth = methPart(tree).symbol if meth.isAllOf(DeferredInline) && !Inliner.inInlineMethod then @@ -3927,6 +3933,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree match { case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[?] | _: Closure => tree + case ByName(body) => + pt match + case ByNameType(underlying) => + ByName(adapt1(body, underlying, locked, tryGadtHealing)) + case _ => + adapt1(body, pt, locked, tryGadtHealing) case _ => tree.tpe.widen match { case tp: FlexType => ensureReported(tp) @@ -3950,14 +3962,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs))) case wtp => val isStructuralCall = wtp.isValueType && isStructuralTermSelectOrApply(tree) - if (isStructuralCall) + if isStructuralCall then readaptSimplified(handleStructural(tree)) + else if needsForcing(tree, wtp, pt) then + readapt(tree.select(nme.apply).appliedToNone) else pt match { case pt: FunProto => if tree.symbol.isAllOf(ApplyProxyFlags) then newExpr else adaptToArgs(wtp, pt) case pt: PolyProto if !wtp.isImplicitMethod => tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply + case ByNameType(underlying) if !wtp.isByName => + ByName(adapt1(tree, pt.widenByName, locked, tryGadtHealing)) case _ => if (ctx.mode is Mode.Type) adaptType(tree.tpe) else adaptNoArgs(wtp) @@ -3966,6 +3982,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } } + /** Is tree an expression with by-name type that should be forced? + * Overridden in ReTyper to always yield `false`. When typechecking source, + * by-name types are always applied implicitly: They are context functions, + * so match up only with using clauses, but they have 0 parameters, whereas + * using clauses cannot be empty. + */ + def needsForcing(tree: Tree, wtp: Type, pt: Type)(using Context): Boolean = + wtp.isByName && !pt.isByName && tree.isTerm + /** True if this inline typer has already issued errors */ def hasInliningErrors(using Context): Boolean = false diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 6d9ff6ca68a8..eaa54d3065b9 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -187,7 +187,7 @@ object QuoteMatcher { // Match a scala.internal.Quoted.patternHole typed as a repeated argument and return the scrutinee tree case Typed(TypeApply(patternHole, tpt :: Nil), tpt2) if patternHole.symbol.eq(defn.QuotedRuntimePatterns_patternHole) && - tpt2.tpe.derivesFrom(defn.RepeatedParamClass) => + tpt2.tpe.isRepeatedParam => scrutinee match case Typed(s, tpt1) if s.tpe <:< tpt.tpe => matched(scrutinee) case _ => notMatched diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index ae3067a894d7..9bda4ed78783 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -930,6 +930,29 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end extension end ReturnMethods + type ByNameArg = tpd.ByName + + object ByNameArgTypeTest extends TypeTest[Tree, ByNameArg]: + def unapply(x: Tree): Option[ByNameArg & x.type] = x match + case tpt: (tpd.ByName & x.type) => Some(tpt) + case _ => None + end ByNameArgTypeTest + + object ByNameArg extends ByNameArgModule: + def apply(result: TypeTree): ByNameArg = + withDefaultPos(tpd.ByName(result)) + def copy(original: Tree)(result: TypeTree): ByNameArg = + tpd.cpy.ByName(original)(result) + def unapply(x: ByNameArg): Some[TypeTree] = + Some(x.expr) + end ByNameArg + + given ByNameArgMethods: ByNameArgMethods with + extension (self: ByNameArg) + def expr: TypeTree = self.expr + end extension + end ByNameArgMethods + type Repeated = tpd.SeqLiteral object RepeatedTypeTest extends TypeTest[Tree, Repeated]: @@ -1734,7 +1757,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def isFunctionType: Boolean = dotc.core.Symbols.defn.isFunctionType(self) def isContextFunctionType: Boolean = - dotc.core.Symbols.defn.isContextFunctionType(self) + dotc.core.Symbols.defn.isContextFunctionType(self) && !self.isByName def isErasedFunctionType: Boolean = dotc.core.Symbols.defn.isErasedFunctionType(self) def isDependentFunctionType: Boolean = @@ -1979,7 +2002,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end extension end MatchTypeMethods - type ByNameType = dotc.core.Types.ExprType + type ByNameType = dotc.core.Types.ExprType // ^^^ change object ByNameTypeTypeTest extends TypeTest[TypeRepr, ByNameType]: def unapply(x: TypeRepr): Option[ByNameType & x.type] = x match diff --git a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala index 0bea8f0ab643..2e9b66246ff9 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala @@ -110,6 +110,8 @@ object Extractors { this += "SummonFrom(" ++= cases += ")" case Return(expr, from) => this += "Return(" += expr += ", " += from += ")" + case ByNameArg(result) => + this += "ByNameArg(" += result += ")" case While(cond, body) => this += "While(" += cond += ", " += body += ")" case Try(block, handlers, finalizer) => diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index d92c16cfe54d..649af918473d 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -512,6 +512,10 @@ object SourceCode { this += "return " printTree(expr) + case ByNameArg(expr) => + this += "/*(by name)*/ " + printTree(expr) + case Repeated(elems, _) => printTrees(elems, ", ") diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 9f19b439135c..6ade4e8dd6e6 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -44,3 +44,7 @@ t6138 t6138-2 i12656.scala trait-static-forwarder + +# failure due to deprecated inline cbn parameters +inline-param-semantics.scala +i1569.scala diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index f5decee041f4..3f95d9880e54 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -1498,6 +1498,32 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end extension end ReturnMethods + /** A delayed argument for a call-by=name parameter */ + type ByNameArg <: Term + + /** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `ByNameArg` */ + given ByNameArgTypeTest: TypeTest[Tree, ByNameArg] + + /** Module object of `a ` argument */ + val ByNameArg: ByNameArgModule + + /** Methods of the module object `val ByNameArg` */ + trait ByNameArgModule { this: ByNameArg.type => + def apply(result: TypeTree): ByNameArg + def copy(original: Tree)(result: TypeTree): ByNameArg + def unapply(x: ByNameArg): Some[TypeTree] + } + + /** Makes extension methods on `ByNameArg` available without any imports */ + given ByNameArgMethods: ByNameArgMethods + + /** Extension methods of `ByNameArg` */ + trait ByNameArgMethods: + extension (self: ByNameArg) + def expr: TypeTree + end extension + end ByNameArgMethods + /** Tree representing a variable argument list in the source code */ type Repeated <: Term diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index 13dfc77ac60b..19d155becba5 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -3,7 +3,7 @@ package scala.runtime.stdLibPatches object Predef: import compiletime.summonFrom - transparent inline def assert(inline assertion: Boolean, inline message: => Any): Unit = + transparent inline def assert(inline assertion: Boolean, inline message: Any): Unit = if !assertion then scala.runtime.Scala3RunTime.assertFailed(message) transparent inline def assert(inline assertion: Boolean): Unit = diff --git a/tests/neg-custom-args/explicit-nulls/byname-nullables.check b/tests/neg-custom-args/explicit-nulls/byname-nullables.check index efe94a496c19..ef4164789b3c 100644 --- a/tests/neg-custom-args/explicit-nulls/byname-nullables.check +++ b/tests/neg-custom-args/explicit-nulls/byname-nullables.check @@ -5,7 +5,7 @@ | Required: String longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/explicit-nulls/byname-nullables.scala:43:32 -------------------------------------------- +-- Error: tests/neg-custom-args/explicit-nulls/byname-nullables.scala:43:24 -------------------------------------------- 43 | if x != null then f(identity(x), 1) // error: dropping not null check fails typing | ^^^^^^^^^^^ | This argument was typed using flow assumptions about mutable variables diff --git a/tests/neg-custom-args/fatal-warnings/i9751.scala b/tests/neg-custom-args/fatal-warnings/i9751.scala index bb7d58957579..28414ffed65b 100644 --- a/tests/neg-custom-args/fatal-warnings/i9751.scala +++ b/tests/neg-custom-args/fatal-warnings/i9751.scala @@ -7,3 +7,8 @@ inline def g(): Unit = { () // error () } + +inline def h(inline x: => Boolean): Unit = + if x then println() + else x + diff --git a/tests/neg-macros/i6976.check b/tests/neg-macros/i6976.check index 2057e55550b4..3bab0123040f 100644 --- a/tests/neg-macros/i6976.check +++ b/tests/neg-macros/i6976.check @@ -2,8 +2,8 @@ -- Error: tests/neg-macros/i6976/Test_2.scala:5:44 --------------------------------------------------------------------- 5 | def main(args: Array[String]): Unit = mcr { 2 } // error | ^^^^^^^^^ - | Exception occurred while executing macro expansion. - | scala.MatchError: Inlined(EmptyTree,List(),Literal(Constant(2))) (of class dotty.tools.dotc.ast.Trees$Inlined) - | at playground.macros$.mcrImpl(Macro_1.scala:10) + | Exception occurred while executing macro expansion. + | scala.MatchError: Inlined(EmptyTree,List(),Ident(x$proxy1)) (of class dotty.tools.dotc.ast.Trees$Inlined) + | at playground.macros$.mcrImpl(Macro_1.scala:10) | | This location contains code that was inlined from Test_2.scala:5 diff --git a/tests/neg/i7969.scala b/tests/neg/i7969.scala deleted file mode 100644 index 72e5609a9931..000000000000 --- a/tests/neg/i7969.scala +++ /dev/null @@ -1,5 +0,0 @@ -object O{ - def f(i: => Int): Int = 1 - def m[A](a: A => Int) = 2 - def n = m(f) // error: cannot eta expand -} \ No newline at end of file diff --git a/tests/pos-macros/null-by-name/Macro_1.scala b/tests/pos-macros/null-by-name/Macro_1.scala new file mode 100644 index 000000000000..7d57990dd136 --- /dev/null +++ b/tests/pos-macros/null-by-name/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def foo(x: => Any): Unit = + ${ impl('x) } + +private def impl(x: Expr[Any])(using Quotes) : Expr[Unit] = { + '{ + val a = $x + } +} diff --git a/tests/pos-macros/null-by-name/Test_2.scala b/tests/pos-macros/null-by-name/Test_2.scala new file mode 100644 index 000000000000..34f5a3111ac6 --- /dev/null +++ b/tests/pos-macros/null-by-name/Test_2.scala @@ -0,0 +1 @@ +def test = foo(null) diff --git a/tests/pos-special/fatal-warnings/i9751b.scala b/tests/pos-special/fatal-warnings/i9751b.scala index 6336b0c47fd4..68d068ce3848 100644 --- a/tests/pos-special/fatal-warnings/i9751b.scala +++ b/tests/pos-special/fatal-warnings/i9751b.scala @@ -4,10 +4,4 @@ object Test { f(true) f(false) - - inline def g(inline x: => Boolean): Unit = - inline if x then println() - - g(true) - g(false) -} +} \ No newline at end of file diff --git a/tests/pos/byname-accessors.scala b/tests/pos/byname-accessors.scala new file mode 100644 index 000000000000..00de130897be --- /dev/null +++ b/tests/pos/byname-accessors.scala @@ -0,0 +1,35 @@ +import scala.collection.mutable.ArrayBuffer +import scala.util.chaining._ + +/** A wrapper for a list of cached instances of a type `T`. + * The wrapper is recursion-reentrant: several instances are kept, so + * at each depth of reentrance we are reusing the instance for that. + * + * An instance is created upon creating this object, and more instances + * are allocated dynamically, on demand, when reentrance occurs. + * + * Not thread safe. + * + * Ported from scala.reflect.internal.util.ReusableInstance + */ +final class ReusableInstance[T <: AnyRef] private (make: => T) { + private[this] val cache = new ArrayBuffer[T](ReusableInstance.InitialSize).tap(_.addOne(make)) + private[this] var taken = 0 + + inline def withInstance[R](action: T => R): R ={ + if (taken == cache.size) + cache += make + taken += 1 + try action(cache(taken-1)) finally taken -= 1 + } +} + +object ReusableInstance { + private inline val InitialSize = 4 + + def apply[T <: AnyRef](make: => T): ReusableInstance[T] = new ReusableInstance[T](make) +} + +object Test extends App: + val ri = ReusableInstance[String]("hi") + ri.withInstance(x => x + x) diff --git a/tests/pos/byname-default.scala b/tests/pos/byname-default.scala new file mode 100644 index 000000000000..0c750fa2a863 --- /dev/null +++ b/tests/pos/byname-default.scala @@ -0,0 +1,5 @@ +// Extraction of a failing case in scalaSTM +object Test: + + def apply[A](init: => A = null.asInstanceOf[A]) = init + diff --git a/tests/pos/byname-namedarg.scala b/tests/pos/byname-namedarg.scala new file mode 100644 index 000000000000..f4f5ac9d33ce --- /dev/null +++ b/tests/pos/byname-namedarg.scala @@ -0,0 +1,5 @@ +object Test: + + def f(x: => Boolean) = x + + f(x = true) diff --git a/tests/pos/i6828.scala b/tests/pos/i6828.scala index 873f6d517a74..c6b205b66564 100644 --- a/tests/pos/i6828.scala +++ b/tests/pos/i6828.scala @@ -1,5 +1,5 @@ class Foo { - inline def foo[T](implicit ct: =>scala.reflect.ClassTag[T]): Unit = () + inline def foo[T](implicit ct: => scala.reflect.ClassTag[T]): Unit = () foo[Int] foo[String] } diff --git a/tests/pos/i7969.scala b/tests/pos/i7969.scala new file mode 100644 index 000000000000..fdd150b56195 --- /dev/null +++ b/tests/pos/i7969.scala @@ -0,0 +1,6 @@ +object O{ + def f(i: => Int): Int = 1 + def m[A](a: A => Int) = 2 + def n = m(f) // was error: cannot eta expand + def nc: (=> Int) => Int = f +} \ No newline at end of file diff --git a/tests/pos/i8182.scala b/tests/pos/i8182.scala index 9acf2941c570..61cb9b8c0268 100644 --- a/tests/pos/i8182.scala +++ b/tests/pos/i8182.scala @@ -1,10 +1,10 @@ -package example +object example: -trait Show[-A]: - extension (a: A) def show: String + trait Show[-A]: + extension (a: A) def show: String -given (using rec: Show[String]): Show[String] = ??? // must be Show[String] as the argument + given str (using rec: Show[String]): Show[String] = ??? // must be Show[String] as the argument -given (using rec: => Show[String]): Show[Option[String]] = ??? // must be byname argument + given laz (using rec: => Show[String]): Show[Option[String]] = ??? // must be byname argument -def test = Option("").show + def test = Option("").show diff --git a/tests/pos/implicit-object-decorators.scala b/tests/pos/implicit-object-decorators.scala new file mode 100644 index 000000000000..517832e002e6 --- /dev/null +++ b/tests/pos/implicit-object-decorators.scala @@ -0,0 +1,17 @@ + +final class EitherObjectOps(private val either: Either.type) extends AnyVal { + def unit[A]: Either[A, Unit] = ??? +} + +trait EitherSyntax { + implicit final def catsSyntaxEitherObject(either: Either.type): EitherObjectOps = + new EitherObjectOps(either) +} +object either extends EitherSyntax + + +object Test: + import either.given + + val x = Either.unit + diff --git a/tests/pos/scala-days-2019-slides/metaprogramming-1-match.scala b/tests/pos/scala-days-2019-slides/metaprogramming-1-match.scala index 62e68f7811ca..301b5df69e26 100644 --- a/tests/pos/scala-days-2019-slides/metaprogramming-1-match.scala +++ b/tests/pos/scala-days-2019-slides/metaprogramming-1-match.scala @@ -4,7 +4,7 @@ object App { case object Zero extends Nat case class Succ[N <: Nat](n: N) extends Nat - inline def toInt(n: => Nat): Int = inline n match { + inline def toInt(inline n: Nat): Int = inline n match { case Zero => 0 case Succ(n1) => toInt(n1) + 1 } diff --git a/tests/run-macros/i5119.check b/tests/run-macros/i5119.check index 25a27ca667e9..33f61af8f7b0 100644 --- a/tests/run-macros/i5119.check +++ b/tests/run-macros/i5119.check @@ -1,2 +1,2 @@ -Select(Typed(Apply(Select(New(TypeIdent("StringContextOps")), ""), List(Apply(Select(Select(Select(Ident("_root_"), "scala"), "StringContext"), "apply"), List(Typed(Repeated(List(Literal(StringConstant("Hello World ")), Literal(StringConstant("!"))), Inferred()), Inferred()))))), TypeIdent("StringContextOps")), "inline$sc") +Apply(Select(Select(Typed(Apply(Select(New(TypeIdent("StringContextOps")), ""), List(Apply(Ident(""), List(Apply(Select(Select(Select(Ident("_root_"), "scala"), "StringContext"), "apply"), List(Typed(Repeated(List(Literal(StringConstant("Hello World ")), Literal(StringConstant("!"))), Inferred()), Inferred()))))))), TypeIdent("StringContextOps")), "inline$sc"), "apply"), Nil) Typed(Repeated(List(Literal(IntConstant(1))), Inferred()), Inferred()) diff --git a/tests/run-macros/tasty-extractors-1.check b/tests/run-macros/tasty-extractors-1.check index bebfa3d79dbc..fedb90cf49b0 100644 --- a/tests/run-macros/tasty-extractors-1.check +++ b/tests/run-macros/tasty-extractors-1.check @@ -115,6 +115,6 @@ TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(DefDef("f8", List(TermParamClause(List(ValDef("i", Annotated(Applied(Inferred(), List(TypeIdent("Int"))), Apply(Select(New(Inferred()), ""), Nil)), None)))), TypeIdent("Int"), Some(Literal(IntConstant(9))))), Apply(Ident("f8"), List(Typed(Repeated(List(Literal(IntConstant(1)), Literal(IntConstant(2)), Literal(IntConstant(3))), Inferred()), Inferred()))))) TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int") -Inlined(None, Nil, Block(List(DefDef("f9", List(TermParamClause(List(ValDef("i", ByName(TypeIdent("Int")), None)))), TypeIdent("Int"), Some(Ident("i")))), Literal(UnitConstant()))) +Inlined(None, Nil, Block(List(DefDef("f9", List(TermParamClause(List(ValDef("i", ByName(TypeIdent("Int")), None)))), TypeIdent("Int"), Some(Apply(Select(Ident("i"), "apply"), Nil)))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") diff --git a/tests/run-macros/tasty-interpolation-1/Macro.scala b/tests/run-macros/tasty-interpolation-1/Macro.scala index 4baa52aa2b90..35405763945f 100644 --- a/tests/run-macros/tasty-interpolation-1/Macro.scala +++ b/tests/run-macros/tasty-interpolation-1/Macro.scala @@ -57,6 +57,13 @@ abstract class MacroStringInterpolator[T] { protected def getStaticStringContext(strCtxExpr: Expr[StringContext])(using Quotes) : StringContext = { import quotes.reflect.* strCtxExpr.asTerm.underlyingArgument match { + case Apply(Select(Select(Typed(Apply(_, List(Apply(Ident(""), List(Apply(Select(Select(Select(Ident("_root_"), "scala"), "StringContext"), "apply"), List(Typed(Repeated(strCtxArgTrees, _), Inferred()))))))), _), _), _), Nil) => + val strCtxArgs = strCtxArgTrees.map { + case Literal(StringConstant(str)) => str + case tree => throw new NotStaticlyKnownError("Expected statically known StringContext", tree.asExpr) + } + StringContext(strCtxArgs*) + // Old style by-name case Select(Typed(Apply(_, List(Apply(_, List(Typed(Repeated(strCtxArgTrees, _), Inferred()))))), _), _) => val strCtxArgs = strCtxArgTrees.map { case Literal(StringConstant(str)) => str diff --git a/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala b/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala index 1aba40352092..26828ebbd263 100644 --- a/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala +++ b/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala @@ -39,7 +39,7 @@ object XmlQuote { val parts: List[String] = stripTyped(receiver.asTerm.underlying) match { case Apply(conv, List(ctx1)) if isSCOpsConversion(conv) => ctx1 match { - case Apply(fun, List(Typed(Repeated(values, _), _))) if isStringContextApply(fun) => + case Apply(Ident(""), List(Apply(fun, List(Typed(Repeated(values, _), _))))) if isStringContextApply(fun) => values.iterator.map { case Literal(StringConstant(value)) => value case _ =>