@@ -159,8 +159,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
159159 * @param required flags the result's symbol must have
160160 * @param excluded flags the result's symbol must not have
161161 * @param pos indicates position to use for error reporting
162+ * @param altImports a ListBuffer in which alternative imported references are
163+ * collected in case `findRef` is called from an expansion of
164+ * an extension method, i.e. when `e.m` is expanded to `m(e)` and
165+ * a reference for `m` is searched. `null` in all other situations.
162166 */
163- def findRef (name : Name , pt : Type , required : FlagSet , excluded : FlagSet , pos : SrcPos )(using Context ): Type = {
167+ def findRef (name : Name , pt : Type , required : FlagSet , excluded : FlagSet , pos : SrcPos ,
168+ altImports : mutable.ListBuffer [TermRef ] | Null = null )(using Context ): Type = {
164169 val refctx = ctx
165170 val noImports = ctx.mode.is(Mode .InPackageClauseName )
166171 def suppressErrors = excluded.is(ConstructorProxy )
@@ -231,15 +236,52 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
231236 fail(AmbiguousReference (name, newPrec, prevPrec, prevCtx))
232237 previous
233238
234- /** Recurse in outer context. If final result is same as `previous`, check that it
235- * is new or shadowed. This order of checking is necessary since an
236- * outer package-level definition might trump two conflicting inner
237- * imports, so no error should be issued in that case. See i7876.scala.
239+ /** Assemble and check alternatives to an imported reference. This implies:
240+ * - If we expand an extension method (i.e. altImports != null),
241+ * search imports on the same level for other possible resolutions of `name`.
242+ * The result and altImports together then contain all possible imported
243+ * references of the highest possible precedence, where `NamedImport` beats
244+ * `WildImport`.
245+ * - Find a posssibly shadowing reference in an outer context.
246+ * If the result is the same as `previous`, check that it is new or
247+ * shadowed. This order of checking is necessary since an outer package-level
248+ * definition might trump two conflicting inner imports, so no error should be
249+ * issued in that case. See i7876.scala.
250+ * @param previous the previously found reference (which is an import)
251+ * @param prevPrec the precedence of the reference (either NamedImport or WildImport)
252+ * @param prevCtx the context in which the reference was found
253+ * @param using_Context the outer context of `precCtx`
238254 */
239- def recurAndCheckNewOrShadowed (previous : Type , prevPrec : BindingPrec , prevCtx : Context )(using Context ): Type =
240- val found = findRefRecur(previous, prevPrec, prevCtx)
241- if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx)
242- else found
255+ def checkImportAlternatives (previous : Type , prevPrec : BindingPrec , prevCtx : Context )(using Context ): Type =
256+
257+ def addAltImport (altImp : TermRef ) =
258+ if ! TypeComparer .isSameRef(previous, altImp)
259+ && ! altImports.uncheckedNN.exists(TypeComparer .isSameRef(_, altImp))
260+ then
261+ altImports.uncheckedNN += altImp
262+
263+ if Feature .enabled(Feature .relaxedExtensionImports) && altImports != null && ctx.isImportContext then
264+ val curImport = ctx.importInfo.uncheckedNN
265+ namedImportRef(curImport) match
266+ case altImp : TermRef =>
267+ if prevPrec == WildImport then
268+ // Discard all previously found references and continue with `altImp`
269+ altImports.clear()
270+ checkImportAlternatives(altImp, NamedImport , ctx)(using ctx.outer)
271+ else
272+ addAltImport(altImp)
273+ checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
274+ case _ =>
275+ if prevPrec == WildImport then
276+ wildImportRef(curImport) match
277+ case altImp : TermRef => addAltImport(altImp)
278+ case _ =>
279+ checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
280+ else
281+ val found = findRefRecur(previous, prevPrec, prevCtx)
282+ if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx)
283+ else found
284+ end checkImportAlternatives
243285
244286 def selection (imp : ImportInfo , name : Name , checkBounds : Boolean ): Type =
245287 imp.importSym.info match
@@ -329,7 +371,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
329371 if (ctx.scope eq EmptyScope ) previous
330372 else {
331373 var result : Type = NoType
332-
333374 val curOwner = ctx.owner
334375
335376 /** Is curOwner a package object that should be skipped?
@@ -450,11 +491,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
450491 else if (isPossibleImport(NamedImport ) && (curImport nen outer.importInfo)) {
451492 val namedImp = namedImportRef(curImport.uncheckedNN)
452493 if (namedImp.exists)
453- recurAndCheckNewOrShadowed (namedImp, NamedImport , ctx)(using outer)
494+ checkImportAlternatives (namedImp, NamedImport , ctx)(using outer)
454495 else if (isPossibleImport(WildImport ) && ! curImport.nn.importSym.isCompleting) {
455496 val wildImp = wildImportRef(curImport.uncheckedNN)
456497 if (wildImp.exists)
457- recurAndCheckNewOrShadowed (wildImp, WildImport , ctx)(using outer)
498+ checkImportAlternatives (wildImp, WildImport , ctx)(using outer)
458499 else {
459500 updateUnimported()
460501 loop(ctx)(using outer)
@@ -3412,11 +3453,37 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34123453 def selectionProto = SelectionProto (tree.name, mbrProto, compat, privateOK = inSelect)
34133454
34143455 def tryExtension (using Context ): Tree =
3415- findRef(tree.name, WildcardType , ExtensionMethod , EmptyFlags , qual.srcPos) match
3456+ val altImports = new mutable.ListBuffer [TermRef ]()
3457+ findRef(tree.name, WildcardType , ExtensionMethod , EmptyFlags , qual.srcPos, altImports) match
34163458 case ref : TermRef =>
3417- extMethodApply(untpd.TypedSplice (tpd.ref(ref).withSpan(tree.nameSpan)), qual, pt)
3459+ def tryExtMethod (ref : TermRef )(using Context ) =
3460+ extMethodApply(untpd.TypedSplice (tpd.ref(ref).withSpan(tree.nameSpan)), qual, pt)
3461+ if altImports.isEmpty then
3462+ tryExtMethod(ref)
3463+ else
3464+ // Try all possible imports and collect successes and failures
3465+ val successes, failures = new mutable.ListBuffer [(Tree , TyperState )]
3466+ for alt <- ref :: altImports.toList do
3467+ val nestedCtx = ctx.fresh.setNewTyperState()
3468+ val app = tryExtMethod(alt)(using nestedCtx)
3469+ (if nestedCtx.reporter.hasErrors then failures else successes)
3470+ += ((app, nestedCtx.typerState))
3471+ typr.println(i " multiple extensioin methods, success: ${successes.toList}, failure: ${failures.toList}" )
3472+
3473+ def pick (alt : (Tree , TyperState )): Tree =
3474+ val (app, ts) = alt
3475+ ts.commit()
3476+ app
3477+
3478+ successes.toList match
3479+ case Nil => pick(failures.head)
3480+ case success :: Nil => pick(success)
3481+ case (expansion1, _) :: (expansion2, _) :: _ =>
3482+ report.error(AmbiguousExtensionMethod (tree, expansion1, expansion2), tree.srcPos)
3483+ expansion1
34183484 case _ =>
34193485 EmptyTree
3486+ end tryExtension
34203487
34213488 def nestedFailure (ex : TypeError ) =
34223489 rememberSearchFailure(qual,
0 commit comments