@@ -158,8 +158,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
158
158
* @param required flags the result's symbol must have
159
159
* @param excluded flags the result's symbol must not have
160
160
* @param pos indicates position to use for error reporting
161
+ * @param altImports a ListBuffer in which alternative imported references are
162
+ * collected in case `findRef` is called from an expansion of
163
+ * an extension method, i.e. when `e.m` is expanded to `m(e)` and
164
+ * a reference for `m` is searched. `null` in all other situations.
161
165
*/
162
- def findRef (name : Name , pt : Type , required : FlagSet , excluded : FlagSet , pos : SrcPos )(using Context ): Type = {
166
+ def findRef (name : Name , pt : Type , required : FlagSet , excluded : FlagSet , pos : SrcPos ,
167
+ altImports : mutable.ListBuffer [TermRef ] | Null = null )(using Context ): Type = {
163
168
val refctx = ctx
164
169
val noImports = ctx.mode.is(Mode .InPackageClauseName )
165
170
def suppressErrors = excluded.is(ConstructorProxy )
@@ -230,15 +235,52 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
230
235
fail(AmbiguousReference (name, newPrec, prevPrec, prevCtx))
231
236
previous
232
237
233
- /** Recurse in outer context. If final result is same as `previous`, check that it
234
- * is new or shadowed. This order of checking is necessary since an
235
- * outer package-level definition might trump two conflicting inner
236
- * imports, so no error should be issued in that case. See i7876.scala.
238
+ /** Assemble and check alternatives to an imported reference. This implies:
239
+ * - If we expand an extension method (i.e. altImports != null),
240
+ * search imports on the same level for other possible resolutions of `name`.
241
+ * The result and altImports together then contain all possible imported
242
+ * references of the highest possible precedence, where `NamedImport` beats
243
+ * `WildImport`.
244
+ * - Find a posssibly shadowing reference in an outer context.
245
+ * If the result is the same as `previous`, check that it is new or
246
+ * shadowed. This order of checking is necessary since an outer package-level
247
+ * definition might trump two conflicting inner imports, so no error should be
248
+ * issued in that case. See i7876.scala.
249
+ * @param previous the previously found reference (which is an import)
250
+ * @param prevPrec the precedence of the reference (either NamedImport or WildImport)
251
+ * @param prevCtx the context in which the reference was found
252
+ * @param using_Context the outer context of `precCtx`
237
253
*/
238
- def recurAndCheckNewOrShadowed (previous : Type , prevPrec : BindingPrec , prevCtx : Context )(using Context ): Type =
239
- val found = findRefRecur(previous, prevPrec, prevCtx)
240
- if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx)
241
- else found
254
+ def checkImportAlternatives (previous : Type , prevPrec : BindingPrec , prevCtx : Context )(using Context ): Type =
255
+
256
+ def addAltImport (altImp : TermRef ) =
257
+ if ! TypeComparer .isSameRef(previous, altImp)
258
+ && ! altImports.uncheckedNN.exists(TypeComparer .isSameRef(_, altImp))
259
+ then
260
+ altImports.uncheckedNN += altImp
261
+
262
+ if altImports != null && ctx.isImportContext then
263
+ val curImport = ctx.importInfo.uncheckedNN
264
+ namedImportRef(curImport) match
265
+ case altImp : TermRef =>
266
+ if prevPrec == WildImport then
267
+ // Discard all previously found references and continue with `altImp`
268
+ altImports.clear()
269
+ checkImportAlternatives(altImp, NamedImport , ctx)(using ctx.outer)
270
+ else
271
+ addAltImport(altImp)
272
+ checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
273
+ case _ =>
274
+ if prevPrec == WildImport then
275
+ wildImportRef(curImport) match
276
+ case altImp : TermRef => addAltImport(altImp)
277
+ case _ =>
278
+ checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
279
+ else
280
+ val found = findRefRecur(previous, prevPrec, prevCtx)
281
+ if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx)
282
+ else found
283
+ end checkImportAlternatives
242
284
243
285
def selection (imp : ImportInfo , name : Name , checkBounds : Boolean ): Type =
244
286
imp.importSym.info match
@@ -328,7 +370,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
328
370
if (ctx.scope eq EmptyScope ) previous
329
371
else {
330
372
var result : Type = NoType
331
-
332
373
val curOwner = ctx.owner
333
374
334
375
/** Is curOwner a package object that should be skipped?
@@ -449,11 +490,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
449
490
else if (isPossibleImport(NamedImport ) && (curImport nen outer.importInfo)) {
450
491
val namedImp = namedImportRef(curImport.uncheckedNN)
451
492
if (namedImp.exists)
452
- recurAndCheckNewOrShadowed (namedImp, NamedImport , ctx)(using outer)
493
+ checkImportAlternatives (namedImp, NamedImport , ctx)(using outer)
453
494
else if (isPossibleImport(WildImport ) && ! curImport.nn.importSym.isCompleting) {
454
495
val wildImp = wildImportRef(curImport.uncheckedNN)
455
496
if (wildImp.exists)
456
- recurAndCheckNewOrShadowed (wildImp, WildImport , ctx)(using outer)
497
+ checkImportAlternatives (wildImp, WildImport , ctx)(using outer)
457
498
else {
458
499
updateUnimported()
459
500
loop(ctx)(using outer)
@@ -3359,11 +3400,37 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
3359
3400
def selectionProto = SelectionProto (tree.name, mbrProto, compat, privateOK = inSelect)
3360
3401
3361
3402
def tryExtension (using Context ): Tree =
3362
- findRef(tree.name, WildcardType , ExtensionMethod , EmptyFlags , qual.srcPos) match
3403
+ val altImports = new mutable.ListBuffer [TermRef ]()
3404
+ findRef(tree.name, WildcardType , ExtensionMethod , EmptyFlags , qual.srcPos, altImports) match
3363
3405
case ref : TermRef =>
3364
- extMethodApply(untpd.TypedSplice (tpd.ref(ref).withSpan(tree.nameSpan)), qual, pt)
3406
+ def tryExtMethod (ref : TermRef )(using Context ) =
3407
+ extMethodApply(untpd.TypedSplice (tpd.ref(ref).withSpan(tree.nameSpan)), qual, pt)
3408
+ if altImports.isEmpty then
3409
+ tryExtMethod(ref)
3410
+ else
3411
+ // Try all possible imports and collect successes and failures
3412
+ val successes, failures = new mutable.ListBuffer [(Tree , TyperState )]
3413
+ for alt <- ref :: altImports.toList do
3414
+ val nestedCtx = ctx.fresh.setNewTyperState()
3415
+ val app = tryExtMethod(alt)(using nestedCtx)
3416
+ (if nestedCtx.reporter.hasErrors then failures else successes)
3417
+ += ((app, nestedCtx.typerState))
3418
+ typr.println(i " multiple extensioin methods, success: ${successes.toList}, failure: ${failures.toList}" )
3419
+
3420
+ def pick (alt : (Tree , TyperState )): Tree =
3421
+ val (app, ts) = alt
3422
+ ts.commit()
3423
+ app
3424
+
3425
+ successes.toList match
3426
+ case Nil => pick(failures.head)
3427
+ case success :: Nil => pick(success)
3428
+ case (expansion1, _) :: (expansion2, _) :: _ =>
3429
+ report.error(AmbiguousExtensionMethod (tree, expansion1, expansion2), tree.srcPos)
3430
+ expansion1
3365
3431
case _ =>
3366
3432
EmptyTree
3433
+ end tryExtension
3367
3434
3368
3435
def nestedFailure (ex : TypeError ) =
3369
3436
rememberSearchFailure(qual,
0 commit comments