@@ -159,8 +159,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
159
159
* @param required flags the result's symbol must have
160
160
* @param excluded flags the result's symbol must not have
161
161
* @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.
162
166
*/
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 = {
164
169
val refctx = ctx
165
170
val noImports = ctx.mode.is(Mode .InPackageClauseName )
166
171
def suppressErrors = excluded.is(ConstructorProxy )
@@ -231,15 +236,52 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
231
236
fail(AmbiguousReference (name, newPrec, prevPrec, prevCtx))
232
237
previous
233
238
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`
238
254
*/
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
243
285
244
286
def selection (imp : ImportInfo , name : Name , checkBounds : Boolean ): Type =
245
287
imp.importSym.info match
@@ -329,7 +371,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
329
371
if (ctx.scope eq EmptyScope ) previous
330
372
else {
331
373
var result : Type = NoType
332
-
333
374
val curOwner = ctx.owner
334
375
335
376
/** Is curOwner a package object that should be skipped?
@@ -450,11 +491,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
450
491
else if (isPossibleImport(NamedImport ) && (curImport nen outer.importInfo)) {
451
492
val namedImp = namedImportRef(curImport.uncheckedNN)
452
493
if (namedImp.exists)
453
- recurAndCheckNewOrShadowed (namedImp, NamedImport , ctx)(using outer)
494
+ checkImportAlternatives (namedImp, NamedImport , ctx)(using outer)
454
495
else if (isPossibleImport(WildImport ) && ! curImport.nn.importSym.isCompleting) {
455
496
val wildImp = wildImportRef(curImport.uncheckedNN)
456
497
if (wildImp.exists)
457
- recurAndCheckNewOrShadowed (wildImp, WildImport , ctx)(using outer)
498
+ checkImportAlternatives (wildImp, WildImport , ctx)(using outer)
458
499
else {
459
500
updateUnimported()
460
501
loop(ctx)(using outer)
@@ -3412,11 +3453,37 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
3412
3453
def selectionProto = SelectionProto (tree.name, mbrProto, compat, privateOK = inSelect)
3413
3454
3414
3455
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
3416
3458
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
3418
3484
case _ =>
3419
3485
EmptyTree
3486
+ end tryExtension
3420
3487
3421
3488
def nestedFailure (ex : TypeError ) =
3422
3489
rememberSearchFailure(qual,
0 commit comments