From 4e028bf37e28cd13cf9c1bf96078a14221947fdc Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 22 Apr 2023 13:55:50 +0200 Subject: [PATCH 1/3] Support extension methods imported from different objects Add a special case to name resolution so that when expanding an extension method from `e.m` to `m(e)` and `m` is imported by several imports on the same level, we try to typecheck under every such import and pick the successful alternative if it exists and is unambiguous. Fixes #16920 --- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 9 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 95 ++++++++++++++++--- .../reference/contextual/extension-methods.md | 11 ++- tests/neg/i13558.check | 26 ----- tests/neg/i16920.check | 88 +++++++++++++++++ tests/neg/i16920.scala | 57 +++++++++++ tests/{neg => pos}/i13558.scala | 4 +- tests/pos/i16920.scala | 76 +++++++++++++++ 9 files changed, 324 insertions(+), 43 deletions(-) delete mode 100644 tests/neg/i13558.check create mode 100644 tests/neg/i16920.check create mode 100644 tests/neg/i16920.scala rename tests/{neg => pos}/i13558.scala (87%) create mode 100644 tests/pos/i16920.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index b3d9b2c1dc49..cc2741bcb614 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -193,6 +193,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ConstrProxyShadowsID // errorNumber 177 case MissingArgumentListID // errorNumber: 178 case MatchTypeScrutineeCannotBeHigherKindedID // errorNumber: 179 + case AmbiguousExtensionMethodID // errorNumber 180 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 1c3bd23de9c6..423c1cdef264 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1434,6 +1434,15 @@ extends ReferenceMsg(AmbiguousOverloadID), NoDisambiguation { |""" } +class AmbiguousExtensionMethod(tree: untpd.Tree, expansion1: tpd.Tree, expansion2: tpd.Tree)(using Context) + extends ReferenceMsg(AmbiguousExtensionMethodID), NoDisambiguation: + def msg(using Context) = + i"""Ambiguous extension methods: + |both $expansion1 + |and $expansion2 + |are possible expansions of $tree""" + def explain(using Context) = "" + class ReassignmentToVal(name: Name)(using Context) extends TypeMsg(ReassignmentToValID) { def msg(using Context) = i"""Reassignment to val $name""" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bfd85a94cc4b..62edd12a00fa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -159,8 +159,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * @param required flags the result's symbol must have * @param excluded flags the result's symbol must not have * @param pos indicates position to use for error reporting + * @param altImports a ListBuffer in which alternative imported references are + * collected in case `findRef` is called from an expansion of + * an extension method, i.e. when `e.m` is expanded to `m(e)` and + * a reference for `m` is searched. `null` in all other situations. */ - def findRef(name: Name, pt: Type, required: FlagSet, excluded: FlagSet, pos: SrcPos)(using Context): Type = { + def findRef(name: Name, pt: Type, required: FlagSet, excluded: FlagSet, pos: SrcPos, + altImports: mutable.ListBuffer[TermRef] | Null = null)(using Context): Type = { val refctx = ctx val noImports = ctx.mode.is(Mode.InPackageClauseName) def suppressErrors = excluded.is(ConstructorProxy) @@ -231,15 +236,52 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer fail(AmbiguousReference(name, newPrec, prevPrec, prevCtx)) previous - /** Recurse in outer context. If final result is same as `previous`, check that it - * is new or shadowed. This order of checking is necessary since an - * outer package-level definition might trump two conflicting inner - * imports, so no error should be issued in that case. See i7876.scala. + /** Assemble and check alternatives to an imported reference. This implies: + * - If we expand an extension method (i.e. altImports != null), + * search imports on the same level for other possible resolutions of `name`. + * The result and altImports together then contain all possible imported + * references of the highest possible precedence, where `NamedImport` beats + * `WildImport`. + * - Find a posssibly shadowing reference in an outer context. + * If the result is the same as `previous`, check that it is new or + * shadowed. This order of checking is necessary since an outer package-level + * definition might trump two conflicting inner imports, so no error should be + * issued in that case. See i7876.scala. + * @param previous the previously found reference (which is an import) + * @param prevPrec the precedence of the reference (either NamedImport or WildImport) + * @param prevCtx the context in which the reference was found + * @param using_Context the outer context of `precCtx` */ - def recurAndCheckNewOrShadowed(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = - val found = findRefRecur(previous, prevPrec, prevCtx) - if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx) - else found + def checkImportAlternatives(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = + + def addAltImport(altImp: TermRef) = + if !TypeComparer.isSameRef(previous, altImp) + && !altImports.uncheckedNN.exists(TypeComparer.isSameRef(_, altImp)) + then + altImports.uncheckedNN += altImp + + if altImports != null && ctx.isImportContext then + val curImport = ctx.importInfo.uncheckedNN + namedImportRef(curImport) match + case altImp: TermRef => + if prevPrec == WildImport then + // Discard all previously found references and continue with `altImp` + altImports.clear() + checkImportAlternatives(altImp, NamedImport, ctx)(using ctx.outer) + else + addAltImport(altImp) + checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer) + case _ => + if prevPrec == WildImport then + wildImportRef(curImport) match + case altImp: TermRef => addAltImport(altImp) + case _ => + checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer) + else + val found = findRefRecur(previous, prevPrec, prevCtx) + if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx) + else found + end checkImportAlternatives def selection(imp: ImportInfo, name: Name, checkBounds: Boolean): Type = imp.importSym.info match @@ -329,7 +371,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (ctx.scope eq EmptyScope) previous else { var result: Type = NoType - val curOwner = ctx.owner /** Is curOwner a package object that should be skipped? @@ -450,11 +491,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else if (isPossibleImport(NamedImport) && (curImport nen outer.importInfo)) { val namedImp = namedImportRef(curImport.uncheckedNN) if (namedImp.exists) - recurAndCheckNewOrShadowed(namedImp, NamedImport, ctx)(using outer) + checkImportAlternatives(namedImp, NamedImport, ctx)(using outer) else if (isPossibleImport(WildImport) && !curImport.nn.importSym.isCompleting) { val wildImp = wildImportRef(curImport.uncheckedNN) if (wildImp.exists) - recurAndCheckNewOrShadowed(wildImp, WildImport, ctx)(using outer) + checkImportAlternatives(wildImp, WildImport, ctx)(using outer) else { updateUnimported() loop(ctx)(using outer) @@ -3412,11 +3453,37 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect) def tryExtension(using Context): Tree = - findRef(tree.name, WildcardType, ExtensionMethod, EmptyFlags, qual.srcPos) match + val altImports = new mutable.ListBuffer[TermRef]() + findRef(tree.name, WildcardType, ExtensionMethod, EmptyFlags, qual.srcPos, altImports) match case ref: TermRef => - extMethodApply(untpd.TypedSplice(tpd.ref(ref).withSpan(tree.nameSpan)), qual, pt) + def tryExtMethod(ref: TermRef)(using Context) = + extMethodApply(untpd.TypedSplice(tpd.ref(ref).withSpan(tree.nameSpan)), qual, pt) + if altImports.isEmpty then + tryExtMethod(ref) + else + // Try all possible imports and collect successes and failures + val successes, failures = new mutable.ListBuffer[(Tree, TyperState)] + for alt <- ref :: altImports.toList do + val nestedCtx = ctx.fresh.setNewTyperState() + val app = tryExtMethod(alt)(using nestedCtx) + (if nestedCtx.reporter.hasErrors then failures else successes) + += ((app, nestedCtx.typerState)) + typr.println(i"multiple extensioin methods, success: ${successes.toList}, failure: ${failures.toList}") + + def pick(alt: (Tree, TyperState)): Tree = + val (app, ts) = alt + ts.commit() + app + + successes.toList match + case Nil => pick(failures.head) + case success :: Nil => pick(success) + case (expansion1, _) :: (expansion2, _) :: _ => + report.error(AmbiguousExtensionMethod(tree, expansion1, expansion2), tree.srcPos) + expansion1 case _ => EmptyTree + end tryExtension def nestedFailure(ex: TypeError) = rememberSearchFailure(qual, diff --git a/docs/_docs/reference/contextual/extension-methods.md b/docs/_docs/reference/contextual/extension-methods.md index 6a1504c25048..f4e75c4456df 100644 --- a/docs/_docs/reference/contextual/extension-methods.md +++ b/docs/_docs/reference/contextual/extension-methods.md @@ -244,7 +244,16 @@ The precise rules for resolving a selection to an extension method are as follow Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, and where `T` is the expected type. The following two rewritings are tried in order: - 1. The selection is rewritten to `m[Ts](e)`. + 1. The selection is rewritten to `m[Ts](e)` and typechecked, using the following + slight modification of the name resolution rules: + + - If `m` is imported by several imports which are all on the nesting level, + try each import as an extension method instead of failing with an ambiguity. + If only one import leads to an expansion that typechecks without errors, pick + that expansion. If there are several such imports, but only one import which is + not a wildcard import, pick the expansion from that import. Otherwise, report + an ambiguous reference error. + 2. If the first rewriting does not typecheck with expected type `T`, and there is an extension method `m` in some eligible object `o`, the selection is rewritten to `o.m[Ts](e)`. An object `o` is _eligible_ if diff --git a/tests/neg/i13558.check b/tests/neg/i13558.check deleted file mode 100644 index 7b4b5215a0a3..000000000000 --- a/tests/neg/i13558.check +++ /dev/null @@ -1,26 +0,0 @@ --- [E008] Not Found Error: tests/neg/i13558.scala:23:14 ---------------------------------------------------------------- -23 | println(a.id) // error - | ^^^^ - | value id is not a member of testcode.A. - | An extension method was tried, but could not be fully constructed: - | - | testcode.ExtensionA.id(a) - | - | failed with: - | - | Reference to id is ambiguous. - | It is both imported by import testcode.ExtensionB._ - | and imported subsequently by import testcode.ExtensionA._ --- [E008] Not Found Error: tests/neg/i13558.scala:29:14 ---------------------------------------------------------------- -29 | println(a.id) // error - | ^^^^ - | value id is not a member of testcode.A. - | An extension method was tried, but could not be fully constructed: - | - | testcode.ExtensionB.id(a) - | - | failed with: - | - | Reference to id is ambiguous. - | It is both imported by import testcode.ExtensionA._ - | and imported subsequently by import testcode.ExtensionB._ diff --git a/tests/neg/i16920.check b/tests/neg/i16920.check new file mode 100644 index 000000000000..86496f24ca3c --- /dev/null +++ b/tests/neg/i16920.check @@ -0,0 +1,88 @@ +-- [E008] Not Found Error: tests/neg/i16920.scala:18:11 ---------------------------------------------------------------- +18 | "five".wow // error + | ^^^^^^^^^^ + | value wow is not a member of String. + | An extension method was tried, but could not be fully constructed: + | + | Two.wow("five") + | + | failed with: + | + | Found: ("five" : String) + | Required: Int +-- [E008] Not Found Error: tests/neg/i16920.scala:26:6 ----------------------------------------------------------------- +26 | 5.wow // error + | ^^^^^ + | value wow is not a member of Int. + | An extension method was tried, but could not be fully constructed: + | + | AlsoFails.wow(5) + | + | failed with: + | + | Found: (5 : Int) + | Required: Boolean +-- [E008] Not Found Error: tests/neg/i16920.scala:27:11 ---------------------------------------------------------------- +27 | "five".wow // error + | ^^^^^^^^^^ + | value wow is not a member of String. + | An extension method was tried, but could not be fully constructed: + | + | AlsoFails.wow("five") + | + | failed with: + | + | Found: ("five" : String) + | Required: Boolean +-- [E008] Not Found Error: tests/neg/i16920.scala:34:6 ----------------------------------------------------------------- +34 | 5.wow // error + | ^^^^^ + | value wow is not a member of Int. + | An extension method was tried, but could not be fully constructed: + | + | Three.wow(5) + | + | failed with: + | + | Ambiguous extension methods: + | both Three.wow(5) + | and Two.wow(5) + | are possible expansions of 5.wow +-- [E008] Not Found Error: tests/neg/i16920.scala:42:11 ---------------------------------------------------------------- +42 | "five".wow // error + | ^^^^^^^^^^ + | value wow is not a member of String. + | An extension method was tried, but could not be fully constructed: + | + | Two.wow("five") + | + | failed with: + | + | Found: ("five" : String) + | Required: Int +-- [E008] Not Found Error: tests/neg/i16920.scala:49:11 ---------------------------------------------------------------- +49 | "five".wow // error + | ^^^^^^^^^^ + | value wow is not a member of String. + | An extension method was tried, but could not be fully constructed: + | + | Two.wow("five") + | + | failed with: + | + | Found: ("five" : String) + | Required: Int +-- [E008] Not Found Error: tests/neg/i16920.scala:56:6 ----------------------------------------------------------------- +56 | 5.wow // error + | ^^^^^ + | value wow is not a member of Int. + | An extension method was tried, but could not be fully constructed: + | + | Three.wow(5) + | + | failed with: + | + | Ambiguous extension methods: + | both Three.wow(5) + | and Two.wow(5) + | are possible expansions of 5.wow diff --git a/tests/neg/i16920.scala b/tests/neg/i16920.scala new file mode 100644 index 000000000000..76953f015de2 --- /dev/null +++ b/tests/neg/i16920.scala @@ -0,0 +1,57 @@ +object One: + extension (s: String) + def wow: Unit = println(s) + +object Two: + extension (i: Int) + def wow: Unit = println(i) + +object Three: + extension (i: Int) + def wow: Unit = println(i) + +object Fails: + import One._ + def test: Unit = + import Two._ + 5.wow + "five".wow // error + +object AlsoFails: + extension (s: Boolean) + def wow = println(s) + import One._ + import Two._ + def test: Unit = + 5.wow // error + "five".wow // error + +object Fails2: + import One._ + import Two._ + import Three._ + def test: Unit = + 5.wow // error + "five".wow // ok + +object Fails3: + import One._ + import Two.wow + def test: Unit = + 5.wow // ok + "five".wow // error + +object Fails4: + import Two.wow + import One._ + def test: Unit = + 5.wow // ok + "five".wow // error + +object Fails5: + import One.wow + import Two.wow + import Three.wow + def test: Unit = + 5.wow // error + "five".wow // ok \ No newline at end of file diff --git a/tests/neg/i13558.scala b/tests/pos/i13558.scala similarity index 87% rename from tests/neg/i13558.scala rename to tests/pos/i13558.scala index 1d4e1c506e43..6f18b770f467 100644 --- a/tests/neg/i13558.scala +++ b/tests/pos/i13558.scala @@ -20,12 +20,12 @@ object Main { import ExtensionB._ import ExtensionA._ val a = A() - println(a.id) // error + println(a.id) // now ok } def main2(args: Array[String]): Unit = { import ExtensionA._ import ExtensionB._ val a = A() - println(a.id) // error + println(a.id) // now ok } } \ No newline at end of file diff --git a/tests/pos/i16920.scala b/tests/pos/i16920.scala new file mode 100644 index 000000000000..b0604b7c9dba --- /dev/null +++ b/tests/pos/i16920.scala @@ -0,0 +1,76 @@ +object One: + extension (s: String) + def wow: Unit = println(s) + +object Two: + extension (i: Int) + def wow: Unit = println(i) + +object Three: + extension (s: String) + def wow: Unit = println(s) + extension (i: Int) + def wow: Unit = println(i) + +object Four: + implicit class WowString(s: String): + def wow: Unit = println(s) + +object Five: + implicit class WowInt(i: Int): + def wow: Unit = println(i) + +object Compiles: + import Three._ + def test: Unit = + 5.wow + "five".wow + +object AlsoCompiles: + import Four._ + import Five._ + def test: Unit = + 5.wow + "five".wow + +object UsedToFail: + import One._ + import Compiles.* + import Two._ + def test: Unit = + 5.wow + "five".wow + +object Conflicting: + extension (i: Int) + def wow: Unit = println(i) + +object Named: + import One.wow + import Two.wow + import Conflicting._ + def test: Unit = + 5.wow // ok + "five".wow // ok + +object Named2: + import Conflicting._ + import One.wow + import Two.wow + def test: Unit = + 5.wow // ok + "five".wow // ok + +val Alias = Two + +object Named3: + import Alias._ + import Two._ + def test: Unit = + 5.wow // ok + +object Named4: + import Two._ + import Alias._ + def test: Unit = + 5.wow // ok From abbd01c27e96b4cb19b2960c48651ec4242a9ed4 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 7 Mar 2023 11:38:02 +0100 Subject: [PATCH 2/3] Put changes under experimental language import As long as this is not SIP approved, we need to put this under experimental. --- .../src/dotty/tools/dotc/config/Feature.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../reference/contextual/extension-methods.md | 4 ++- .../runtime/stdLibPatches/language.scala | 8 +++++ tests/neg/i13558.scala | 31 +++++++++++++++++++ tests/neg/i16920.check | 28 ++++++++--------- tests/neg/i16920.scala | 2 ++ tests/pos/i13558.scala | 1 + tests/pos/i16920.scala | 2 ++ 9 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 tests/neg/i13558.scala diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 419ed5868cbf..e5ab8f65f55b 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -29,6 +29,7 @@ object Feature: val fewerBraces = experimental("fewerBraces") val saferExceptions = experimental("saferExceptions") val clauseInterleaving = experimental("clauseInterleaving") + val relaxedExtensionImports = experimental("relaxedExtensionImports") val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") val into = experimental("into") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 62edd12a00fa..4bc012b5b226 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -260,7 +260,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then altImports.uncheckedNN += altImp - if altImports != null && ctx.isImportContext then + if Feature.enabled(Feature.relaxedExtensionImports) && altImports != null && ctx.isImportContext then val curImport = ctx.importInfo.uncheckedNN namedImportRef(curImport) match case altImp: TermRef => diff --git a/docs/_docs/reference/contextual/extension-methods.md b/docs/_docs/reference/contextual/extension-methods.md index f4e75c4456df..d98d80caafc5 100644 --- a/docs/_docs/reference/contextual/extension-methods.md +++ b/docs/_docs/reference/contextual/extension-methods.md @@ -252,7 +252,9 @@ The following two rewritings are tried in order: If only one import leads to an expansion that typechecks without errors, pick that expansion. If there are several such imports, but only one import which is not a wildcard import, pick the expansion from that import. Otherwise, report - an ambiguous reference error. + an ambiguous reference error. + + **Note**: This relaxation is currently enabled only under the `experimental.relaxedExtensionImports` language import. 2. If the first rewriting does not typecheck with expected type `T`, and there is an extension method `m` in some eligible object `o`, the selection is rewritten to `o.m[Ts](e)`. An object `o` is _eligible_ if diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index d92495c6f5aa..091e75fa06e1 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -69,6 +69,14 @@ object language: @compileTimeOnly("`clauseInterleaving` can only be used at compile time in import statements") object clauseInterleaving + /** Adds support for relaxed imports of extension methods. + * Extension methods with the same name can be imported from several places. + * + * @see [[http://dotty.epfl.ch/docs/reference/contextual/extension-methods]] + */ + @compileTimeOnly("`relaxedExtensionImports` can only be used at compile time in import statements") + object relaxedExtensionImports + /** Experimental support for pure function type syntax * * @see [[https://dotty.epfl.ch/docs/reference/experimental/purefuns]] diff --git a/tests/neg/i13558.scala b/tests/neg/i13558.scala new file mode 100644 index 000000000000..1d4e1c506e43 --- /dev/null +++ b/tests/neg/i13558.scala @@ -0,0 +1,31 @@ +package testcode + +class A + +class B + +object ExtensionA { + extension (self: A) { + def id = "A" + } +} +object ExtensionB { + extension (self: B) { + def id = "B" + } +} + +object Main { + def main1(args: Array[String]): Unit = { + import ExtensionB._ + import ExtensionA._ + val a = A() + println(a.id) // error + } + def main2(args: Array[String]): Unit = { + import ExtensionA._ + import ExtensionB._ + val a = A() + println(a.id) // error + } +} \ No newline at end of file diff --git a/tests/neg/i16920.check b/tests/neg/i16920.check index 86496f24ca3c..131ba4c6265e 100644 --- a/tests/neg/i16920.check +++ b/tests/neg/i16920.check @@ -1,5 +1,5 @@ --- [E008] Not Found Error: tests/neg/i16920.scala:18:11 ---------------------------------------------------------------- -18 | "five".wow // error +-- [E008] Not Found Error: tests/neg/i16920.scala:20:11 ---------------------------------------------------------------- +20 | "five".wow // error | ^^^^^^^^^^ | value wow is not a member of String. | An extension method was tried, but could not be fully constructed: @@ -10,8 +10,8 @@ | | Found: ("five" : String) | Required: Int --- [E008] Not Found Error: tests/neg/i16920.scala:26:6 ----------------------------------------------------------------- -26 | 5.wow // error +-- [E008] Not Found Error: tests/neg/i16920.scala:28:6 ----------------------------------------------------------------- +28 | 5.wow // error | ^^^^^ | value wow is not a member of Int. | An extension method was tried, but could not be fully constructed: @@ -22,8 +22,8 @@ | | Found: (5 : Int) | Required: Boolean --- [E008] Not Found Error: tests/neg/i16920.scala:27:11 ---------------------------------------------------------------- -27 | "five".wow // error +-- [E008] Not Found Error: tests/neg/i16920.scala:29:11 ---------------------------------------------------------------- +29 | "five".wow // error | ^^^^^^^^^^ | value wow is not a member of String. | An extension method was tried, but could not be fully constructed: @@ -34,8 +34,8 @@ | | Found: ("five" : String) | Required: Boolean --- [E008] Not Found Error: tests/neg/i16920.scala:34:6 ----------------------------------------------------------------- -34 | 5.wow // error +-- [E008] Not Found Error: tests/neg/i16920.scala:36:6 ----------------------------------------------------------------- +36 | 5.wow // error | ^^^^^ | value wow is not a member of Int. | An extension method was tried, but could not be fully constructed: @@ -48,8 +48,8 @@ | both Three.wow(5) | and Two.wow(5) | are possible expansions of 5.wow --- [E008] Not Found Error: tests/neg/i16920.scala:42:11 ---------------------------------------------------------------- -42 | "five".wow // error +-- [E008] Not Found Error: tests/neg/i16920.scala:44:11 ---------------------------------------------------------------- +44 | "five".wow // error | ^^^^^^^^^^ | value wow is not a member of String. | An extension method was tried, but could not be fully constructed: @@ -60,8 +60,8 @@ | | Found: ("five" : String) | Required: Int --- [E008] Not Found Error: tests/neg/i16920.scala:49:11 ---------------------------------------------------------------- -49 | "five".wow // error +-- [E008] Not Found Error: tests/neg/i16920.scala:51:11 ---------------------------------------------------------------- +51 | "five".wow // error | ^^^^^^^^^^ | value wow is not a member of String. | An extension method was tried, but could not be fully constructed: @@ -72,8 +72,8 @@ | | Found: ("five" : String) | Required: Int --- [E008] Not Found Error: tests/neg/i16920.scala:56:6 ----------------------------------------------------------------- -56 | 5.wow // error +-- [E008] Not Found Error: tests/neg/i16920.scala:58:6 ----------------------------------------------------------------- +58 | 5.wow // error | ^^^^^ | value wow is not a member of Int. | An extension method was tried, but could not be fully constructed: diff --git a/tests/neg/i16920.scala b/tests/neg/i16920.scala index 76953f015de2..38345e811c1f 100644 --- a/tests/neg/i16920.scala +++ b/tests/neg/i16920.scala @@ -1,3 +1,5 @@ +import language.experimental.relaxedExtensionImports + object One: extension (s: String) def wow: Unit = println(s) diff --git a/tests/pos/i13558.scala b/tests/pos/i13558.scala index 6f18b770f467..0c8be379f6a9 100644 --- a/tests/pos/i13558.scala +++ b/tests/pos/i13558.scala @@ -1,4 +1,5 @@ package testcode +import language.experimental.relaxedExtensionImports class A diff --git a/tests/pos/i16920.scala b/tests/pos/i16920.scala index b0604b7c9dba..dd4f5804a4fd 100644 --- a/tests/pos/i16920.scala +++ b/tests/pos/i16920.scala @@ -1,3 +1,5 @@ +import language.experimental.relaxedExtensionImports + object One: extension (s: String) def wow: Unit = println(s) From 81e9e1b09b44fd338270dd7c1ec08f567847de40 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 7 Mar 2023 13:28:19 +0100 Subject: [PATCH 3/3] Update MiMaFilters --- project/MiMaFilters.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index cb15d82affb8..d53eeb7077a4 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -26,6 +26,8 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$clauseInterleaving$"), ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.into"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$into$"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.relaxedExtensionImports"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$relaxedExtensionImports$"), // end of New experimental features in 3.3.X // Added java.io.Serializable as LazyValControlState supertype