Skip to content

Commit

Permalink
Reorganise (& rename) typedSelectWithAdapt
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dwijnand committed Jun 11, 2024
1 parent f7ab683 commit a63abc1
Showing 1 changed file with 115 additions and 94 deletions.
209 changes: 115 additions & 94 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
* `<context-bound-companion>[ref_1 | ... | ref_N]` as described by
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit a63abc1

Please sign in to comment.