diff --git a/compiler/src/dotty/tools/dotc/cc/SafeRefs.scala b/compiler/src/dotty/tools/dotc/cc/SafeRefs.scala index c6f4a9090796..592ef3d51134 100644 --- a/compiler/src/dotty/tools/dotc/cc/SafeRefs.scala +++ b/compiler/src/dotty/tools/dotc/cc/SafeRefs.scala @@ -7,13 +7,15 @@ import Symbols.* import Annotations.* import util.Spans.NoSpan import util.{Property, SrcPos} -import Contexts.Context +import Contexts.{Context, ctx} import Constants.Constant import Decorators.* import ast.tpd.* import SymDenotations.* import Flags.* import Types.* +import Names.Name +import NameOps.isReplWrapperName import config.Printers.capt /** Check whether references from safe mode should be allowed */ @@ -172,6 +174,10 @@ object SafeRefs { rejectSafe("scala.runtime.LazyUnit") } + /** Allow name in safe mode even though it contains `$` characters */ + def allowDollarIn(name: Name)(using Context): Boolean = + name.isReplWrapperName && ctx.mode.is(Mode.Interactive) + private def fail(sym: Symbol, reason: String, pos: SrcPos)(using Context) = report.error(em"Cannot refer to ${sym.sanitizedDescription}${sym.showExtendedLocation} from safe code since $reason", pos) false diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 842ec7f7cfb3..32ba1dc36765 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -19,6 +19,7 @@ import config.Feature.{migrateTo3, sourceVersion} import config.SourceVersion.{`3.0`, `3.0-migration`} import config.MigrationVersion import reporting.* +import cc.SafeRefs import java.util.Objects import dotty.tools.dotc.reporting.Message.rewriteNotice @@ -146,8 +147,11 @@ object Scanners { * If `target` is different from `this`, don't treat identifiers as end tokens. */ def finishNamedToken(idtoken: Token, target: TokenData): Unit = - target.name = termName(litBuf.chars, 0, litBuf.length) + val name = termName(litBuf.chars, 0, litBuf.length) + target.name = name litBuf.clear() + if name.contains('$') && Feature.safeEnabled && !SafeRefs.allowDollarIn(name) then + report.error(em"Identifier may not contain '$$' in safe mode", sourcePos()) target.token = idtoken if idtoken == IDENTIFIER then val converted = toToken(target.name.nn) diff --git a/compiler/test/dotc/neg-best-effort-pickling.excludelist b/compiler/test/dotc/neg-best-effort-pickling.excludelist index f3c761193a47..73cbd7841991 100644 --- a/compiler/test/dotc/neg-best-effort-pickling.excludelist +++ b/compiler/test/dotc/neg-best-effort-pickling.excludelist @@ -21,5 +21,8 @@ i974.scala # cyclic reference i13864.scala # missing symbol in pickling type-params.scala # recursion limit exceeded +unsafe-dollar-in-name.scala # missing owner in unpickler +unsafe-dollar-in-name-2.scala # missing owner in unpickler + # semantic db generation fails in the first compilation i15158.scala # cyclic reference - stack overflow diff --git a/tests/neg/unsafe-dollar-in-name-2.check b/tests/neg/unsafe-dollar-in-name-2.check new file mode 100644 index 000000000000..b8926a0bc821 --- /dev/null +++ b/tests/neg/unsafe-dollar-in-name-2.check @@ -0,0 +1,8 @@ +-- Error: tests/neg/unsafe-dollar-in-name-2.scala:3:7 ------------------------------------------------------------------ +3 |object Foo$ // error + | ^ + | Identifier may not contain '$' in safe mode +-- Error: tests/neg/unsafe-dollar-in-name-2.scala:5:6 ------------------------------------------------------------------ +5 |class `Bar$` // error + | ^ + | Identifier may not contain '$' in safe mode diff --git a/tests/neg/unsafe-dollar-in-name-2.scala b/tests/neg/unsafe-dollar-in-name-2.scala new file mode 100644 index 000000000000..787c696e5d02 --- /dev/null +++ b/tests/neg/unsafe-dollar-in-name-2.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.safe + +object Foo$ // error + +class `Bar$` // error diff --git a/tests/neg/unsafe-dollar-in-name.check b/tests/neg/unsafe-dollar-in-name.check new file mode 100644 index 000000000000..e0c1f179e284 --- /dev/null +++ b/tests/neg/unsafe-dollar-in-name.check @@ -0,0 +1,8 @@ +-- Error: tests/neg/unsafe-dollar-in-name.scala:3:7 -------------------------------------------------------------------- +3 |object Foo$ // error + | ^ + | Identifier may not contain '$' in safe mode +-- Error: tests/neg/unsafe-dollar-in-name.scala:5:6 -------------------------------------------------------------------- +5 |class `Bar$` // error + | ^ + | Identifier may not contain '$' in safe mode diff --git a/tests/neg/unsafe-dollar-in-name.scala b/tests/neg/unsafe-dollar-in-name.scala new file mode 100644 index 000000000000..687479164c9c --- /dev/null +++ b/tests/neg/unsafe-dollar-in-name.scala @@ -0,0 +1,5 @@ +import language.experimental.safe + +object Foo$ // error + +class `Bar$` // error diff --git a/tests/neg/wildcard-imports.check b/tests/neg/wildcard-imports.check new file mode 100644 index 000000000000..96f35062844d --- /dev/null +++ b/tests/neg/wildcard-imports.check @@ -0,0 +1,8 @@ +-- [E049] Reference Error: tests/neg/wildcard-imports/Test.scala:7:12 -------------------------------------------------- +7 | val bar = foo // error + | ^^^ + | Reference to foo is ambiguous. + | It is both imported by import test.A._ + | and imported subsequently by import test.B._ + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/wildcard-imports/A.scala b/tests/neg/wildcard-imports/A.scala new file mode 100644 index 000000000000..9ee9e1fddec3 --- /dev/null +++ b/tests/neg/wildcard-imports/A.scala @@ -0,0 +1,4 @@ +package test + +object A: + def foo: Int = 0 diff --git a/tests/neg/wildcard-imports/B.scala b/tests/neg/wildcard-imports/B.scala new file mode 100644 index 000000000000..036822c1e34c --- /dev/null +++ b/tests/neg/wildcard-imports/B.scala @@ -0,0 +1,4 @@ +package test + +object B: + def foo: Int = 0 diff --git a/tests/neg/wildcard-imports/Test.scala b/tests/neg/wildcard-imports/Test.scala new file mode 100644 index 000000000000..022654e3f71e --- /dev/null +++ b/tests/neg/wildcard-imports/Test.scala @@ -0,0 +1,8 @@ +package test + +import A.* +import B.* + +object Test: + val bar = foo // error +