From d78c157d60da890827b424212fd5861b66182608 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 17 Aug 2023 17:35:50 +0200 Subject: [PATCH 1/3] Add `defn.FunctionNOf.{apply,unapply}` This provides variant to `defn.FunctionOf` that only deals with proper `FunctionN` and `ContextFunctionN` types. This avoids some overhead. A difference between the two `unapply`s is that this one does not dealias the type, it needs to be dealiased at call site. Part of #18305 --- .../src/dotty/tools/dotc/cc/Synthetics.scala | 4 +-- .../dotty/tools/dotc/core/Definitions.scala | 25 ++++++++++++++++--- .../dotty/tools/dotc/core/TypeErasure.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/transform/PickleQuotes.scala | 2 +- .../dotc/transform/SpecializeFunctions.scala | 2 +- .../tools/dotc/transform/TreeChecker.scala | 4 +-- .../dotty/tools/dotc/typer/Applications.scala | 14 +++++------ .../dotty/tools/dotc/typer/ProtoTypes.scala | 6 ++--- .../tools/dotc/typer/QuotesAndSplices.scala | 4 +-- .../dotty/tools/dotc/typer/Synthesizer.scala | 2 +- 11 files changed, 42 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 1e7c8d641238..c4c52513fb49 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -174,9 +174,9 @@ object Synthetics: val (et: ExprType) = symd.info: @unchecked val (enclThis: ThisType) = symd.owner.thisType: @unchecked def mapFinalResult(tp: Type, f: Type => Type): Type = - val defn.FunctionOf(args, res, isContextual) = tp: @unchecked + val defn.FunctionNOf(args, res, isContextual) = tp: @unchecked if defn.isFunctionNType(res) then - defn.FunctionOf(args, mapFinalResult(res, f), isContextual) + defn.FunctionNOf(args, mapFinalResult(res, f), isContextual) else f(tp) val resType1 = diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b4df6bcd4ca5..fcd55cd71f8c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1111,10 +1111,9 @@ class Definitions { object FunctionOf { def apply(args: List[Type], resultType: Type, isContextual: Boolean = false)(using Context): Type = val mt = MethodType.companion(isContextual, false)(args, resultType) - if mt.hasErasedParams then - RefinedType(PolyFunctionClass.typeRef, nme.apply, mt) - else - FunctionType(args.length, isContextual).appliedTo(args ::: resultType :: Nil) + if mt.hasErasedParams then RefinedType(PolyFunctionClass.typeRef, nme.apply, mt) + else FunctionNOf(args, resultType, isContextual) + def unapply(ft: Type)(using Context): Option[(List[Type], Type, Boolean)] = { ft.dealias match case PolyFunctionOf(mt: MethodType) => @@ -1129,6 +1128,24 @@ class Definitions { } } + object FunctionNOf { + /** Create a `FunctionN` or `ContextFunctionN` type applied to the arguments and result type */ + def apply(args: List[Type], resultType: Type, isContextual: Boolean = false)(using Context): Type = + FunctionType(args.length, isContextual).appliedTo(args ::: resultType :: Nil) + + /** Matches a (possibly aliased) `FunctionN[...]` or `ContextFunctionN[...]`. + * Extracts the list of function argument types, the result type and whether function is contextual. + */ + def unapply(tpe: Type)(using Context): Option[(List[Type], Type, Boolean)] = { + val tsym = tpe.typeSymbol + if isFunctionSymbol(tsym) && tpe.isRef(tsym) then + val targs = tpe.argInfos + if (targs.isEmpty) None + else Some(targs.init, targs.last, tsym.name.isContextFunction) + else None + } + } + object RefinedFunctionOf { /** Matches a refined `PolyFunction`/`FunctionN[...]`/`ContextFunctionN[...]`. * Extracts the method type type and apply info. diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 94c7b2993b97..3e67135a842b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -933,7 +933,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst case tp: TermRef => sigName(underlyingOfTermRef(tp)) case ExprType(rt) => - sigName(defn.FunctionOf(Nil, rt)) + sigName(defn.FunctionNOf(Nil, rt)) case tp: TypeVar if !tp.isInstantiated => tpnme.Uninstantiated case tp @ defn.PolyFunctionOf(_) => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 15b7695202e1..cfa3551c0cf0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1890,7 +1890,7 @@ object Types { case res: MethodType => res.toFunctionType(isJava) case res => res } - defn.FunctionOf( + defn.FunctionNOf( mt.paramInfos.mapConserve(_.translateFromRepeated(toArray = isJava)), result1, isContextual) if mt.hasErasedParams then diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index b368e47bf0b3..791d461add7a 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -326,7 +326,7 @@ object PickleQuotes { defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), args => val cases = holeContents.zipWithIndex.map { case (splice, idx) => - val defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _), _) = splice.tpe: @unchecked + val defn.FunctionNOf(argTypes, defn.FunctionNOf(quotesType :: _, _, _), _) = splice.tpe: @unchecked val rhs = { val spliceArgs = argTypes.zipWithIndex.map { (argType, i) => args(1).select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argType) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala index c50eaddd3213..9d757dc9713c 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -88,7 +88,7 @@ class SpecializeFunctions extends MiniPhase { // Need to cast to regular function, since specialized apply methods // are not members of ContextFunction0. The cast will be eliminated in // erasure. - qual.cast(defn.FunctionOf(Nil, res)) + qual.cast(defn.FunctionNOf(Nil, res)) case _ => qual qual1.select(specializedApply) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index f84f628fc981..dd32dde93f95 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -749,9 +749,9 @@ object TreeChecker { if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt) else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt) val contextualResult = - defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) + defn.FunctionNOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) val expectedContentType = - defn.FunctionOf(argQuotedTypes, contextualResult) + defn.FunctionNOf(argQuotedTypes, contextualResult) assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") tree1 diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 921e3ca86fe4..aa5665a5c891 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1724,7 +1724,7 @@ trait Applications extends Compatibility { def apply(t: Type) = t match { case t @ AppliedType(tycon, args) => def mapArg(arg: Type, tparam: TypeParamInfo) = - if (variance > 0 && tparam.paramVarianceSign < 0) defn.FunctionOf(arg :: Nil, defn.UnitType) + if (variance > 0 && tparam.paramVarianceSign < 0) defn.FunctionNOf(arg :: Nil, defn.UnitType) else arg mapOver(t.derivedAppliedType(tycon, args.zipWithConserve(tycon.typeParams)(mapArg))) case _ => mapOver(t) @@ -1951,7 +1951,7 @@ trait Applications extends Compatibility { /** The shape of given tree as a type; cannot handle named arguments. */ def typeShape(tree: untpd.Tree): Type = tree match { case untpd.Function(args, body) => - defn.FunctionOf( + defn.FunctionNOf( args.map(Function.const(defn.AnyType)), typeShape(body), isContextual = untpd.isContextualClosure(tree)) case Match(EmptyTree, _) => @@ -1991,8 +1991,8 @@ trait Applications extends Compatibility { def paramCount(ref: TermRef) = val formals = ref.widen.firstParamTypes if formals.length > idx then - formals(idx) match - case defn.FunctionOf(args, _, _) => args.length + formals(idx).dealias match + case defn.FunctionNOf(args, _, _) => args.length case _ => -1 else -1 @@ -2077,8 +2077,8 @@ trait Applications extends Compatibility { else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1) case pt => - val compat0 = pt match - case defn.FunctionOf(args, resType, _) => + val compat0 = pt.dealias match + case defn.FunctionNOf(args, resType, _) => narrowByTypes(alts, args, resType) case _ => Nil @@ -2266,7 +2266,7 @@ trait Applications extends Compatibility { false val commonFormal = if (isPartial) defn.PartialFunctionOf(commonParamTypes.head, WildcardType) - else defn.FunctionOf(commonParamTypes, WildcardType, isContextual = untpd.isContextualClosure(arg)) + else defn.FunctionNOf(commonParamTypes, WildcardType, isContextual = untpd.isContextualClosure(arg)) overload.println(i"pretype arg $arg with expected type $commonFormal") if (commonParamTypes.forall(isFullyDefined(_, ForceDegree.flipBottom))) withMode(Mode.ImplicitsEnabled) { diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 7303124b0cd4..14f97e324b86 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -383,9 +383,9 @@ object ProtoTypes { def allArgTypesAreCurrent()(using Context): Boolean = state.typedArg.size == args.length - private def isUndefined(tp: Type): Boolean = tp match { + private def isUndefined(tp: Type): Boolean = tp.dealias match { case _: WildcardType => true - case defn.FunctionOf(args, result, _) => args.exists(isUndefined) || isUndefined(result) + case defn.FunctionNOf(args, result, _) => args.exists(isUndefined) || isUndefined(result) case _ => false } @@ -424,7 +424,7 @@ object ProtoTypes { case ValDef(_, tpt, _) if !tpt.isEmpty => typer.typedType(tpt).typeOpt case _ => WildcardType } - targ = arg.withType(defn.FunctionOf(paramTypes, WildcardType)) + targ = arg.withType(defn.FunctionNOf(paramTypes, WildcardType)) case Some(_) if !force => targ = arg.withType(WildcardType) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 28afccd1ca43..a172eb290f7a 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -122,7 +122,7 @@ trait QuotesAndSplices { for arg <- typedArgs if arg.symbol.is(Mutable) do // TODO support these patterns. Possibly using scala.quoted.util.Var report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos) val argTypes = typedArgs.map(_.tpe.widenTermRefExpr) - val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt) + val patType = if tree.args.isEmpty then pt else defn.FunctionNOf(argTypes, pt) val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(using quotePatternSpliceContext) val baseType = pat.tpe.baseType(defn.QuotedExprClass) val argType = if baseType.exists then baseType.argTypesHi.head else defn.NothingType @@ -148,7 +148,7 @@ trait QuotesAndSplices { if isInBraces then // ${x}(...) match an application val typedArgs = args.map(arg => typedExpr(arg)) val argTypes = typedArgs.map(_.tpe.widenTermRefExpr) - val splice1 = typedSplicePattern(splice, defn.FunctionOf(argTypes, pt)) + val splice1 = typedSplicePattern(splice, defn.FunctionNOf(argTypes, pt)) untpd.cpy.Apply(tree)(splice1.select(nme.apply), typedArgs).withType(pt) else // $x(...) higher-order quasipattern if args.isEmpty then diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index cbb13a841946..c15a6da0b701 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -105,7 +105,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case AppliedType(_, funArgs @ fun :: tupled :: Nil) => def functionTypeEqual(baseFun: Type, actualArgs: List[Type], actualRet: Type, expected: Type) = - expected =:= defn.FunctionOf(actualArgs, actualRet, + expected =:= defn.FunctionNOf(actualArgs, actualRet, defn.isContextFunctionType(baseFun)) val arity: Int = if defn.isFunctionNType(fun) then From bbd8d81170780fee90d6c4f4aaa910eb0544872c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 30 Aug 2023 11:38:46 +0200 Subject: [PATCH 2/3] Optimize `FunctionNOf.unapply` --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index fcd55cd71f8c..944712b6cd73 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1136,13 +1136,10 @@ class Definitions { /** Matches a (possibly aliased) `FunctionN[...]` or `ContextFunctionN[...]`. * Extracts the list of function argument types, the result type and whether function is contextual. */ - def unapply(tpe: Type)(using Context): Option[(List[Type], Type, Boolean)] = { - val tsym = tpe.typeSymbol - if isFunctionSymbol(tsym) && tpe.isRef(tsym) then - val targs = tpe.argInfos - if (targs.isEmpty) None - else Some(targs.init, targs.last, tsym.name.isContextFunction) - else None + def unapply(tpe: AppliedType)(using Context): Option[(List[Type], Type, Boolean)] = { + val targs = tpe.args + if targs.isEmpty || !isFunctionNType(tpe) then None + else Some(targs.init, targs.last, tpe.typeSymbol.name.isContextFunction) } } From 1e7243a658816d050a6b7c31a1f44620fd94026f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 31 Aug 2023 17:55:17 +0200 Subject: [PATCH 3/3] Remove unnecessary guard --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 944712b6cd73..7b34eea94ec7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1137,9 +1137,8 @@ class Definitions { * Extracts the list of function argument types, the result type and whether function is contextual. */ def unapply(tpe: AppliedType)(using Context): Option[(List[Type], Type, Boolean)] = { - val targs = tpe.args - if targs.isEmpty || !isFunctionNType(tpe) then None - else Some(targs.init, targs.last, tpe.typeSymbol.name.isContextFunction) + if !isFunctionNType(tpe) then None + else Some(tpe.args.init, tpe.args.last, tpe.typeSymbol.name.isContextFunction) } }