diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index a293b0e9c1bc..ec85fbdb766b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1027,9 +1027,11 @@ object Trees { protected def sourceFile(tree: Tree): SourceFile = tree.source protected def finalize(tree: Tree, copied: untpd.Tree): copied.ThisTree[T] = + Stats.record(s"TreeCopier.finalize/${tree.getClass == copied.getClass}") postProcess(tree, copied.withSpan(tree.span).withAttachmentsFrom(tree)) protected def finalize(tree: Tree, copied: untpd.MemberDef): copied.ThisTree[T] = + Stats.record(s"TreeCopier.finalize/${tree.getClass == copied.getClass}") postProcess(tree, copied.withSpan(tree.span).withAttachmentsFrom(tree)) def Ident(tree: Tree)(name: Name)(using Context): Ident = tree match { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ff731f171dc4..88849ff6f798 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1389,7 +1389,7 @@ class Definitions { !tp.isInstanceOf[RefinedType] } - /** Is `tp` a representation of a (possibly depenent) function type or an alias of such? */ + /** Is `tp` a representation of a (possibly dependent) function type or an alias of such? */ def isFunctionType(tp: Type)(using Context): Boolean = isNonRefinedFunction(tp.dropDependentRefinement) @@ -1440,13 +1440,12 @@ class Definitions { * - the upper bound of a TypeParamRef in the current constraint */ def asContextFunctionType(tp: Type)(using Context): Type = - tp.stripTypeVar.dealias match { + tp.stripTypeVar.dealias match case tp1: TypeParamRef if ctx.typerState.constraint.contains(tp1) => asContextFunctionType(TypeComparer.bounds(tp1).hiBound) case tp1 => - if (isFunctionType(tp1) && tp1.typeSymbol.name.isContextFunction) tp1 + if tp1.typeSymbol.name.isContextFunction && isFunctionType(tp1) then tp1 else NoType - } /** Is `tp` an context function type? */ def isContextFunctionType(tp: Type)(using Context): Boolean = @@ -1468,7 +1467,7 @@ class Definitions { else None def isErasedFunctionType(tp: Type)(using Context): Boolean = - isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction + tp.dealias.typeSymbol.name.isErasedFunction && isFunctionType(tp) /** A whitelist of Scala-2 classes that are known to be pure */ def isAssuredNoInits(sym: Symbol): Boolean = diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index a90ee6a46fab..b1ec1ed8c89d 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -169,56 +169,89 @@ object NameOps { } } - def functionArity: Int = - functionArityFor(str.Function) max - functionArityFor(str.ContextFunction) max { - val n = - functionArityFor(str.ErasedFunction) max - functionArityFor(str.ErasedContextFunction) - if (n == 0) -1 else n - } + private def functionSuffixStart: Int = + val first = name.firstPart + var idx = first.length - 1 + if idx >= 8 && first(idx).isDigit then + while + idx = idx - 1 + idx >= 8 && first(idx).isDigit + do () + if first(idx - 7) == 'F' + && first(idx - 6) == 'u' + && first(idx - 5) == 'n' + && first(idx - 4) == 'c' + && first(idx - 3) == 't' + && first(idx - 2) == 'i' + && first(idx - 1) == 'o' + && first(idx) == 'n' + then idx - 7 + else -1 + else -1 + + /** The arity of a name ending in the suffix `Function{\d}`, but -1 + * if the number is larger than Int.MaxValue / 10. + * @param suffixStart The index of the suffix + */ + private def funArity(suffixStart: Int): Int = + inline val MaxSafeInt = MaxValue / 10 + val first = name.firstPart + def collectDigits(acc: Int, idx: Int): Int = + if idx == first.length then acc + else + val d = digit2int(first(idx), 10) + if d < 0 || acc > MaxSafeInt then -1 + else collectDigits(acc * 10 + d, idx + 1) + collectDigits(0, suffixStart + 8) - /** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN for N >= 0 or ErasedFunctionN, ErasedContextFunctionN for N > 0 + /** name[0..suffixStart) == `str` */ + private def isPreceded(str: String, suffixStart: Int) = + str.length == suffixStart && name.firstPart.startsWith(str) + + /** Same as `funArity`, except that it returns -1 if the prefix + * is not one of "", "Context", "Erased", "ErasedContext" + */ + private def checkedFunArity(suffixStart: Int) = + if suffixStart == 0 + || isPreceded("Context", suffixStart) + || isPreceded("Erased", suffixStart) + || isPreceded("ErasedContext", suffixStart) + then funArity(suffixStart) + else -1 + + /** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN, ErasedFunctionN, ErasedContextFunctionN for N >= 0 */ - def isFunction: Boolean = (name eq tpnme.FunctionXXL) || functionArity >= 0 + def isFunction: Boolean = + (name eq tpnme.FunctionXXL) || checkedFunArity(functionSuffixStart) >= 0 - /** Is an context function name, i.e one of ContextFunctionN for N >= 0 or ErasedContextFunctionN for N > 0 + /** Is an context function name, i.e one of ContextFunctionN or ErasedContextFunctionN for N >= 0 */ def isContextFunction: Boolean = - functionArityFor(str.ContextFunction) >= 0 || - functionArityFor(str.ErasedContextFunction) > 0 + val suffixStart = functionSuffixStart + (isPreceded("Context", suffixStart) || isPreceded("ErasedContext", suffixStart)) + && funArity(suffixStart) >= 0 - /** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N > 0 + /** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N >= 0 */ def isErasedFunction: Boolean = - functionArityFor(str.ErasedFunction) > 0 || - functionArityFor(str.ErasedContextFunction) > 0 + val suffixStart = functionSuffixStart + (isPreceded("Erased", suffixStart) || isPreceded("ErasedContext", suffixStart)) + && funArity(suffixStart) >= 0 /** Is a synthetic function name, i.e. one of * - FunctionN for N > 22 * - ContextFunctionN for N >= 0 - * - ErasedFunctionN for N > 0 - * - ErasedContextFunctionN for N > 0 + * - ErasedFunctionN for N >= 0 + * - ErasedContextFunctionN for N >= 0 */ def isSyntheticFunction: Boolean = - functionArityFor(str.Function) > MaxImplementedFunctionArity || - functionArityFor(str.ContextFunction) >= 0 || - isErasedFunction + val suffixStart = functionSuffixStart + if suffixStart == 0 then funArity(suffixStart) > MaxImplementedFunctionArity + else checkedFunArity(suffixStart) >= 0 - /** Parsed function arity for function with some specific prefix */ - private def functionArityFor(prefix: String): Int = - inline val MaxSafeInt = MaxValue / 10 - val first = name.firstPart - def collectDigits(acc: Int, idx: Int): Int = - if idx == first.length then acc - else - val d = digit2int(first(idx), 10) - if d < 0 || acc > MaxSafeInt then -1 - else collectDigits(acc * 10 + d, idx + 1) - if first.startsWith(prefix) && prefix.length < first.length then - collectDigits(0, prefix.length) - else - -1 + def functionArity: Int = + val suffixStart = functionSuffixStart + if suffixStart >= 0 then checkedFunArity(suffixStart) else -1 /** The name of the generic runtime operation corresponding to an array operation */ def genericArrayOp: TermName = name match { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ddd8278e160a..386d7a448ac1 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -181,7 +181,7 @@ object Types { * It makes no sense for it to be an alias type because isRef would always * return false in that case. */ - def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = stripAnnots.stripTypeVar match { + def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = stripped match { case this1: TypeRef => this1.info match { // see comment in Namer#typeDefSig case TypeAlias(tp) => tp.isRef(sym, skipRefined) @@ -196,7 +196,7 @@ object Types { case _ => false } - /** Is this type a (neither aliased nor applied) reference to class `sym`? */ + /** Is this type a (neither aliased nor applied nor annotated) reference to class `sym`? */ def isDirectRef(sym: Symbol)(using Context): Boolean = stripTypeVar match { case this1: TypeRef => this1.name == sym.name && // avoid forcing info if names differ @@ -369,7 +369,7 @@ object Types { /** Is this a match type or a higher-kinded abstraction of one? */ - def isMatch(using Context): Boolean = stripTypeVar.stripAnnots match { + def isMatch(using Context): Boolean = stripped match { case _: MatchType => true case tp: HKTypeLambda => tp.resType.isMatch case _ => false @@ -1070,9 +1070,12 @@ object Types { def stripTypeVar(using Context): Type = this /** Remove all AnnotatedTypes wrapping this type. - */ + */ def stripAnnots(using Context): Type = this + /** Strip TypeVars and Annotation wrappers */ + def stripped(using Context): Type = this + def rewrapAnnots(tp: Type)(using Context): Type = tp.stripTypeVar match { case AnnotatedType(tp1, annot) => AnnotatedType(rewrapAnnots(tp1), annot) case _ => this @@ -1098,15 +1101,23 @@ object Types { * def o: Outer * .widen = o.C */ - final def widen(using Context): Type = widenSingleton match { + final def widen(using Context): Type = this match + case _: TypeRef | _: MethodOrPoly => this // fast path for most frequent cases + case tp: TermRef => // fast path for next most frequent case + if tp.isOverloaded then tp else tp.underlying.widen + case tp: SingletonType => tp.underlying.widen case tp: ExprType => tp.resultType.widen - case tp => tp - } + case tp => + val tp1 = tp.stripped + if tp1 eq tp then tp + else + val tp2 = tp1.widen + if tp2 ne tp1 then tp2 else tp /** Widen from singleton type to its underlying non-singleton * base type by applying one or more `underlying` dereferences. */ - final def widenSingleton(using Context): Type = stripTypeVar.stripAnnots match { + final def widenSingleton(using Context): Type = stripped match { case tp: SingletonType if !tp.isOverloaded => tp.underlying.widenSingleton case _ => this } @@ -4294,6 +4305,8 @@ object Types { if (inst.exists) inst.stripTypeVar else origin } + override def stripped(using Context): Type = stripTypeVar.stripped + /** If the variable is instantiated, its instance, otherwise its origin */ override def underlying(using Context): Type = { val inst = instanceOpt @@ -4697,6 +4710,8 @@ object Types { override def stripAnnots(using Context): Type = parent.stripAnnots + override def stripped(using Context): Type = parent.stripped + private var isRefiningKnown = false private var isRefiningCache: Boolean = _ diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 6491da4f5efc..0787266eef4c 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -691,7 +691,7 @@ class SpaceEngine(using Context) extends SpaceLogic { case _ => tp.show } - def refine(tp: Type): String = tp.stripAnnots.stripTypeVar match { + def refine(tp: Type): String = tp.stripped match { case tp: RefinedType => refine(tp.parent) case tp: AppliedType => refine(tp.typeConstructor) + ( diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1376199c9162..25e5597b9d95 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1447,7 +1447,7 @@ trait Applications extends Compatibility { * that takes fewer. */ def compare(alt1: TermRef, alt2: TermRef)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) { - record("compare") + record("resolveOverloaded.compare") /** Is alternative `alt1` with type `tp1` as specific as alternative * `alt2` with type `tp2` ? @@ -1754,7 +1754,7 @@ trait Applications extends Compatibility { */ private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) { - record("resolveOverloaded1") + record(s"resolveOverloaded1", alts.length) def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty @@ -1840,8 +1840,8 @@ trait Applications extends Compatibility { alts.filterConserve(sizeFits(_)) def narrowByShapes(alts: List[TermRef]): List[TermRef] = - val normArgs = args.mapWithIndexConserve(normArg(alts, _, _)) - if normArgs.exists(untpd.isFunctionWithUnknownParamType) then + if args.exists(untpd.isFunctionWithUnknownParamType) then + val normArgs = args.mapWithIndexConserve(normArg(alts, _, _)) if hasNamedArg(args) then narrowByTrees(alts, normArgs.map(treeShape), resultType) else narrowByTypes(alts, normArgs.map(typeShape), resultType) else @@ -1859,14 +1859,17 @@ trait Applications extends Compatibility { alts2 } + record("resolveOverloaded.FunProto", alts.length) val alts1 = narrowBySize(alts) //report.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %") if isDetermined(alts1) then alts1 else + record("resolveOverloaded.narrowedBySize", alts1.length) val alts2 = narrowByShapes(alts1) //report.log(i"narrowed by shape: ${alts2.map(_.symbol.showDcl)}%, %") if isDetermined(alts2) then alts2 else + record("resolveOverloaded.narrowedByShape", alts2.length) pretypeArgs(alts2, pt) narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType) @@ -1915,6 +1918,7 @@ trait Applications extends Compatibility { case tp: MethodType => stripImplicit(tp.resultType).isInstanceOf[MethodType] case _ => false + record("resolveOverloaded.narrowedApplicable", candidates.length) val found = narrowMostSpecific(candidates) if (found.length <= 1) found else