From a63abc140207c00da4e95ad1ddd6b7ec57d4fc04 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Mar 2024 13:06:07 +0000 Subject: [PATCH] Reorganise (& rename) typedSelectWithAdapt Prior to the next commit, I broke up the logic into internal methods, so some can be reused, consuming then in a big Tree#orElse chain. I also took the opportunity to rename the method, to more easily distinguish it from the other typedSelect. --- .../src/dotty/tools/dotc/typer/Typer.scala | 209 ++++++++++-------- 1 file changed, 115 insertions(+), 94 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1c779ac050fa..265b9e09f908 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -724,137 +724,158 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then report.error(StableIdentPattern(tree, pt), tree.srcPos) - def typedSelect(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = + def typedSelectWithAdapt(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = val selName = tree0.name val tree = cpy.Select(tree0)(qual, selName) val superAccess = qual.isInstanceOf[Super] val rawType = selectionType(tree, qual) - val checkedType = accessibleType(rawType, superAccess) - def finish(tree: untpd.Select, qual: Tree, checkedType: Type): Tree = - val select = toNotNullTermRef(assignType(tree, checkedType), pt) - if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix") - checkLegalValue(select, pt) - ConstFold(select) - - // If regular selection is typeable, we are done - if checkedType.exists then - return finish(tree, qual, checkedType) + def tryType(tree: untpd.Select, qual: Tree, rawType: Type) = + val checkedType = accessibleType(rawType, superAccess) + // If regular selection is typeable, we are done + if checkedType.exists then + val select = toNotNullTermRef(assignType(tree, checkedType), pt) + if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix") + checkLegalValue(select, pt) + ConstFold(select) + else EmptyTree // Otherwise, simplify `m.apply(...)` to `m(...)` - if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then - return qual + def trySimplifyApply() = + if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then + qual + else EmptyTree // Otherwise, if there's a simply visible type variable in the result, try again // with a more defined qualifier type. There's a second trial where we try to instantiate // all type variables in `qual.tpe.widen`, but that is done only after we search for // extension methods or conversions. - if couldInstantiateTypeVar(qual.tpe.widen) then - // there's a simply visible type variable in the result; try again with a more defined qualifier type - // There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`, - // but that is done only after we search for extension methods or conversions. - return typedSelect(tree, pt, qual) + def tryInstantiateTypeVar() = + if couldInstantiateTypeVar(qual.tpe.widen) then + // there's a simply visible type variable in the result; try again with a more defined qualifier type + // There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`, + // but that is done only after we search for extension methods or conversions. + typedSelectWithAdapt(tree, pt, qual) + else EmptyTree // Otherwise, try to expand a named tuple selection - val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes - val nameIdx = namedTupleElems.indexWhere(_._1 == selName) - if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then - return typed( - untpd.Apply( - untpd.Select(untpd.TypedSplice(qual), nme.apply), - untpd.Literal(Constant(nameIdx))), - pt) + def tryNamedTupleSelection() = + val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes + val nameIdx = namedTupleElems.indexWhere(_._1 == selName) + if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then + typed( + untpd.Apply( + untpd.Select(untpd.TypedSplice(qual), nme.apply), + untpd.Literal(Constant(nameIdx))), + pt) + else EmptyTree // Otherwise, map combinations of A *: B *: .... EmptyTuple with nesting levels <= 22 // to the Tuple class of the right arity and select from that one - if qual.tpe.isSmallGenericTuple then - val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil) - return typedSelect(tree, pt, qual.cast(defn.tupleType(elems))) + def trySmallGenericTuple(qual: Tree, withCast: Boolean) = + if qual.tpe.isSmallGenericTuple then + if withCast then + val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil) + typedSelectWithAdapt(tree, pt, qual.cast(defn.tupleType(elems))) + else + typedSelectWithAdapt(tree, pt, qual) + else EmptyTree // Otherwise try an extension or conversion - if selName.isTermName then - val tree1 = tryExtensionOrConversion( + def tryExt(tree: untpd.Select, qual: Tree) = + tryExtensionOrConversion( tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true) - if !tree1.isEmpty then - return tree1 // Otherwise, try a GADT approximation if we're trying to select a member - // Member lookup cannot take GADTs into account b/c of cache, so we - // approximate types based on GADT constraints instead. For an example, - // see MemberHealing in gadt-approximation-interaction.scala. - if ctx.gadt.isNarrowing then - val wtp = qual.tpe.widen - gadts.println(i"Trying to heal member selection by GADT-approximating $wtp") - val gadtApprox = Inferencing.approximateGADT(wtp) - gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox") - val qual1 = qual.cast(gadtApprox) - val tree1 = cpy.Select(tree0)(qual1, selName) - val checkedType1 = accessibleType(selectionType(tree1, qual1), superAccess = false) - if checkedType1.exists then - gadts.println(i"Member selection healed by GADT approximation") - return finish(tree1, qual1, checkedType1) - - if qual1.tpe.isSmallGenericTuple then - gadts.println(i"Tuple member selection healed by GADT approximation") - return typedSelect(tree, pt, qual1) - - val tree2 = tryExtensionOrConversion(tree1, pt, IgnoredProto(pt), qual1, ctx.typerState.ownedVars, this, inSelect = true) - if !tree2.isEmpty then - return tree2 + def tryGadt() = + if ctx.gadt.isNarrowing then + // Member lookup cannot take GADTs into account b/c of cache, so we + // approximate types based on GADT constraints instead. For an example, + // see MemberHealing in gadt-approximation-interaction.scala. + val wtp = qual.tpe.widen + gadts.println(i"Trying to heal member selection by GADT-approximating $wtp") + val gadtApprox = Inferencing.approximateGADT(wtp) + gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox") + val qual1 = qual.cast(gadtApprox) + val tree1 = cpy.Select(tree0)(qual1, selName) + tryType(tree1, qual1, selectionType(tree1, qual1)) + .orElse(trySmallGenericTuple(qual1, withCast = false)) + .orElse(tryExt(tree1, qual1)) + else EmptyTree // Otherwise, if there are uninstantiated type variables in the qualifier type, // instantiate them and try again - if canDefineFurther(qual.tpe.widen) then - return typedSelect(tree, pt, qual) + def tryDefineFurther() = + if canDefineFurther(qual.tpe.widen) then + typedSelectWithAdapt(tree, pt, qual) + else EmptyTree def dynamicSelect(pt: Type) = - val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) - if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then - assignType(tree2, TryDynamicCallType) - else - typedDynamicSelect(tree2, Nil, pt) + val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) + if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then + assignType(tree2, TryDynamicCallType) + else + typedDynamicSelect(tree2, Nil, pt) // Otherwise, if the qualifier derives from class Dynamic, expand to a // dynamic dispatch using selectDynamic or applyDynamic - if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then - return dynamicSelect(pt) + def tryDynamic() = + if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then + dynamicSelect(pt) + else EmptyTree // Otherwise, if the qualifier derives from class Selectable, // and the selector name matches one of the element of the `Fields` type member, // and the selector is not assigned to, // expand to a typed dynamic dispatch using selectDynamic wrapped in a cast - if qual.tpe.derivesFrom(defn.SelectableClass) && !isDynamicExpansion(tree) - && pt != LhsProto - then - val pre = if !TypeOps.isLegalPrefix(qual.tpe) then SkolemType(qual.tpe) else qual.tpe - val fieldsType = pre.select(tpnme.Fields).dealias.simplified - val fields = fieldsType.namedTupleElementTypes - typr.println(i"try dyn select $qual, $selName, $fields") - fields.find(_._1 == selName) match - case Some((_, fieldType)) => - val dynSelected = dynamicSelect(fieldType) - dynSelected match - case Apply(sel: Select, _) if !sel.denot.symbol.exists => - // Reject corner case where selectDynamic needs annother selectDynamic to be called. E.g. as in neg/unselectable-fields.scala. - report.error(i"Cannot use selectDynamic here since it needs another selectDynamic to be invoked", tree.srcPos) - case _ => - return dynSelected.ensureConforms(fieldType) - case _ => + def trySelectable() = + if qual.tpe.derivesFrom(defn.SelectableClass) && !isDynamicExpansion(tree) + && pt != LhsProto + then + val pre = if !TypeOps.isLegalPrefix(qual.tpe) then SkolemType(qual.tpe) else qual.tpe + val fieldsType = pre.select(tpnme.Fields).dealias.simplified + val fields = fieldsType.namedTupleElementTypes + typr.println(i"try dyn select $qual, $selName, $fields") + fields.find(_._1 == selName) match + case Some((_, fieldType)) => + val dynSelected = dynamicSelect(fieldType) + dynSelected match + case Apply(sel: Select, _) if !sel.denot.symbol.exists => + // Reject corner case where selectDynamic needs annother selectDynamic to be called. E.g. as in neg/unselectable-fields.scala. + report.error(i"Cannot use selectDynamic here since it needs another selectDynamic to be invoked", tree.srcPos) + case _ => + dynSelected.ensureConforms(fieldType) + case _ => EmptyTree + else EmptyTree // Otherwise, if the qualifier is a context bound companion, handle // by selecting a witness in typedCBSelect - if qual.tpe.typeSymbol == defn.CBCompanion then - val witnessSelection = typedCBSelect(tree0, pt, qual) - if !witnessSelection.isEmpty then return witnessSelection + def tryCBCompanion() = + if qual.tpe.typeSymbol == defn.CBCompanion then + typedCBSelect(tree0, pt, qual) + else EmptyTree // Otherwise, report an error - assignType(tree, - rawType match - case rawType: NamedType => - inaccessibleErrorType(rawType, superAccess, tree.srcPos) - case _ => - notAMemberErrorType(tree, qual, pt)) - end typedSelect + def reportAnError() = + assignType(tree, + rawType match + case rawType: NamedType => + inaccessibleErrorType(rawType, superAccess, tree.srcPos) + case _ => + notAMemberErrorType(tree, qual, pt)) + + tryType(tree, qual, rawType) + .orElse(trySimplifyApply()) + .orElse(tryInstantiateTypeVar()) + .orElse(tryNamedTupleSelection()) + .orElse(trySmallGenericTuple(qual, withCast = true)) + .orElse(tryExt(tree, qual)) + .orElse(tryGadt()) + .orElse(tryDefineFurther()) + .orElse(tryDynamic()) + .orElse(tryCBCompanion()) + .orElse(reportAnError()) + end typedSelectWithAdapt /** Expand a selection A.m on a context bound companion A with type * `[ref_1 | ... | ref_N]` as described by @@ -906,7 +927,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case witness: TermRef => val altQual = tpd.ref(witness).withSpan(qual.span) val altCtx = ctx.fresh.setNewTyperState() - val alt = typedSelect(tree, pt, altQual)(using altCtx) + val alt = typedSelectWithAdapt(tree, pt, altQual)(using altCtx) def current = (alt, altCtx.typerState, witness) if altCtx.reporter.hasErrors then prevs else @@ -938,7 +959,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if ctx.isJava then javaSelection(qual) else - typedSelect(tree, pt, qual).withSpan(tree.span).computeNullable() + typedSelectWithAdapt(tree, pt, qual).withSpan(tree.span).computeNullable() def javaSelection(qual: Tree)(using Context) = val tree1 = assignType(cpy.Select(tree)(qual, tree.name), qual) @@ -3879,7 +3900,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if isExtension then return found else checkImplicitConversionUseOK(found, selProto) - return withoutMode(Mode.ImplicitsEnabled)(typedSelect(tree, pt, found)) + return withoutMode(Mode.ImplicitsEnabled)(typedSelectWithAdapt(tree, pt, found)) case failure: SearchFailure => if failure.isAmbiguous then return