Skip to content

Commit 40d3b08

Browse files
som-snytttgodzik
authored andcommitted
Warn about encoded pkg obj names
1 parent 2b8f5fa commit 40d3b08

File tree

17 files changed

+123
-33
lines changed

17 files changed

+123
-33
lines changed

compiler/src/dotty/tools/dotc/Driver.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import dotty.tools.io.AbstractFile
1010
import reporting.*
1111
import core.Decorators.*
1212
import config.Feature
13+
import util.chaining.*
1314

1415
import scala.util.control.NonFatal
1516
import fromtasty.{TASTYCompiler, TastyFileUtil}
@@ -82,10 +83,12 @@ class Driver {
8283
if !ctx.settings.YdropComments.value || ctx.settings.YreadComments.value then
8384
ictx.setProperty(ContextDoc, new ContextDocstrings)
8485
val fileNamesOrNone = command.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)
85-
fileNamesOrNone.map { fileNames =>
86+
fileNamesOrNone.map: fileNames =>
8687
val files = fileNames.map(ctx.getFile)
8788
(files, fromTastySetup(files))
88-
}
89+
.tap: _ =>
90+
if ctx.settings.YnoReporter.value then
91+
ictx.setReporter(Reporter.SilentReporter())
8992
}
9093
}
9194

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,25 +1024,27 @@ object desugar {
10241024
else tree
10251025
}
10261026

1027-
def checkPackageName(mdef: ModuleDef | PackageDef)(using Context): Unit =
1028-
1029-
def check(name: Name, errSpan: Span): Unit = name match
1030-
case name: SimpleName if !errSpan.isSynthetic && name.exists(Chars.willBeEncoded) =>
1031-
report.warning(em"The package name `$name` will be encoded on the classpath, and can lead to undefined behaviour.", mdef.source.atSpan(errSpan))
1032-
case _ =>
1033-
1034-
def loop(part: RefTree): Unit = part match
1035-
case part @ Ident(name) => check(name, part.span)
1036-
case part @ Select(qual: RefTree, name) =>
1037-
check(name, part.nameSpan)
1038-
loop(qual)
1027+
def checkSimplePackageName(name: Name, errSpan: Span, source: SourceFile, isPackageObject: Boolean)(using Context) =
1028+
if !ctx.isAfterTyper then
1029+
name match
1030+
case name: SimpleName if (isPackageObject || !errSpan.isSynthetic) && name.exists(Chars.willBeEncoded) =>
1031+
report.warning(EncodedPackageName(name), source.atSpan(errSpan))
10391032
case _ =>
10401033

1034+
def checkPackageName(mdef: ModuleDef | PackageDef)(using Context): Unit =
1035+
def check(name: Name, errSpan: Span) = checkSimplePackageName(name, errSpan, mdef.source, isPackageObject = false)
10411036
mdef match
1042-
case pdef: PackageDef => loop(pdef.pid)
1043-
case mdef: ModuleDef if mdef.mods.is(Package) => check(mdef.name, mdef.nameSpan)
1044-
case _ =>
1045-
end checkPackageName
1037+
case pdef: PackageDef =>
1038+
def loop(part: RefTree): Unit = part match
1039+
case part @ Ident(name) => check(name, part.span)
1040+
case part @ Select(qual: RefTree, name) =>
1041+
check(name, part.nameSpan)
1042+
loop(qual)
1043+
case _ =>
1044+
loop(pdef.pid)
1045+
case mdef: ModuleDef if mdef.mods.is(Package) =>
1046+
check(mdef.name, mdef.nameSpan)
1047+
case _ =>
10461048

10471049
/** The normalized name of `mdef`. This means
10481050
* 1. Check that the name does not redefine a Scala core class.
@@ -1420,7 +1422,7 @@ object desugar {
14201422
/** Assuming `src` contains top-level definition, returns the name that should
14211423
* be using for the package object that will wrap them.
14221424
*/
1423-
def packageObjectName(src: SourceFile): TermName =
1425+
def packageObjectName(src: SourceFile, srcPos: SrcPos)(using Context): TermName =
14241426
val fileName = src.file.name
14251427
val sourceName = fileName.take(fileName.lastIndexOf('.'))
14261428
(sourceName ++ str.TOPLEVEL_SUFFIX).toTermName
@@ -1451,7 +1453,7 @@ object desugar {
14511453
val (nestedStats, topStats) = pdef.stats.partition(inPackageObject)
14521454
if (nestedStats.isEmpty) pdef
14531455
else {
1454-
val name = packageObjectName(ctx.source)
1456+
val name = packageObjectName(ctx.source, pdef.srcPos)
14551457
val grouped =
14561458
ModuleDef(name, Template(emptyConstructor, Nil, Nil, EmptyValDef, nestedStats))
14571459
.withMods(Modifiers(Synthetic))

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ private sealed trait YSettings:
377377
val YdisableFlatCpCaching: Setting[Boolean] = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
378378

379379
val Yscala2Unpickler: Setting[String] = StringSetting("-Yscala2-unpickler", "", "Control where we may get Scala 2 symbols from. This is either \"always\", \"never\", or a classpath.", "always")
380+
val YnoReporter: Setting[Boolean] = BooleanSetting("Yno-reporter", "Diagnostics are silently consumed")
380381

381382
val YnoImports: Setting[Boolean] = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.")
382383
val Yimports: Setting[List[String]] = MultiStringSetting("-Yimports", helpArg="", "Custom root imports. If set, none of scala.*, java.lang.*, or Predef.* will be imported unless explicitly included.")

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
235235
case CannotInstantiateQuotedTypeVarID // errorNumber: 219
236236
case DefaultShadowsGivenID // errorNumber: 220
237237
case RecurseWithDefaultID // errorNumber: 221
238+
case EncodedPackageNameID // 222
238239

239240
def errorNumber = ordinal - 1
240241

compiler/src/dotty/tools/dotc/reporting/Reporter.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ object Reporter {
3030
override def report(dia: Diagnostic)(using Context): Unit = ()
3131
}
3232

33+
/** A reporter for -Yno-reporter [sic] */
34+
class SilentReporter extends Reporter:
35+
def doReport(dia: Diagnostic)(using Context): Unit = ()
36+
3337
type ErrorHandler = (Diagnostic, Context) => Unit
3438

3539
private val defaultIncompleteHandler: ErrorHandler =

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3391,4 +3391,16 @@ final class RecurseWithDefault(name: Name)(using Context) extends TypeMsg(Recurs
33913391
override protected def msg(using Context): String =
33923392
i"Recursive call used a default argument for parameter $name."
33933393
override protected def explain(using Context): String =
3394-
"It's more explicit to pass current or modified arguments in a recursion."
3394+
"It's more explicit to pass current or modified arguments in a recursion."
3395+
3396+
final class EncodedPackageName(name: Name)(using Context) extends SyntaxMsg(EncodedPackageNameID):
3397+
override protected def msg(using Context): String =
3398+
i"The package name `$name` will be encoded on the classpath, and can lead to undefined behaviour."
3399+
override protected def explain(using Context): String =
3400+
i"""Tooling may not cope with directories whose names do not match their package name.
3401+
|For example, `p-q` is encoded as `p$$minusq` and written that way to the file system.
3402+
|
3403+
|Package objects have names derived from their file names, so that names such as
3404+
|`myfile.test.scala` and `myfile-test.scala` will result in encoded names for package objects.
3405+
|
3406+
|The name `$name` is encoded to `${name.encode}`."""

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import EtaExpansion.etaExpand
3131
import TypeComparer.CompareResult
3232
import inlines.{Inlines, PrepareInlineable}
3333
import util.Spans.*
34+
import util.chaining.*
3435
import util.common.*
3536
import util.{Property, SimpleIdentityMap, SrcPos}
3637
import Applications.{tupleComponentTypes, wrapDefs, defaultArgument}
@@ -2921,12 +2922,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
29212922
inContext(ctx.packageContext(tree, pkg)) {
29222923
// If it exists, complete the class containing the top-level definitions
29232924
// before typing any statement in the package to avoid cycles as in i13669.scala
2924-
val topLevelClassName = desugar.packageObjectName(ctx.source).moduleClassName
2925-
pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted()
2925+
val packageObjectName = desugar.packageObjectName(ctx.source, tree.srcPos)
2926+
val topLevelClassSymbol = pkg.moduleClass.info.decls.lookup(packageObjectName.moduleClassName)
2927+
topLevelClassSymbol.ensureCompleted()
29262928
var stats1 = typedStats(tree.stats, pkg.moduleClass)._1
29272929
if (!ctx.isAfterTyper)
29282930
stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1
29292931
cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef)
2932+
.tap: _ =>
2933+
if !ctx.isAfterTyper
2934+
&& pkg != defn.EmptyPackageVal
2935+
&& !topLevelClassSymbol.info.decls.filter(sym => !sym.isConstructor && !sym.is(Synthetic)).isEmpty
2936+
then
2937+
desugar.checkSimplePackageName(packageObjectName, tree.span, ctx.source, isPackageObject = true)
29302938
}
29312939
case _ =>
29322940
// Package will not exist if a duplicate type has already been entered, see `tests/neg/1708.scala`
@@ -3333,8 +3341,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
33333341
def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(using Context): (List[Tree], Context) = {
33343342
val buf = new mutable.ListBuffer[Tree]
33353343
var enumContexts: SimpleIdentityMap[Symbol, Context] = SimpleIdentityMap.empty
3336-
val initialNotNullInfos = ctx.notNullInfos
33373344
// A map from `enum` symbols to the contexts enclosing their definitions
3345+
val initialNotNullInfos = ctx.notNullInfos
33383346
@tailrec def traverse(stats: List[untpd.Tree])(using Context): (List[Tree], Context) = stats match {
33393347
case (imp: untpd.Import) :: rest =>
33403348
val imp1 = typed(imp)

compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ class SemanticdbTests:
143143
"-classpath", target.toString,
144144
"-Xignore-scala2-macros",
145145
"-usejavacp",
146-
"-Wunused:all"
146+
"-Wunused:all",
147+
"-Yno-reporter",
147148
) ++ inputFiles().map(_.toString)
148149
val exit = Main.process(args)
149150
assertFalse(s"dotc errors: ${exit.errorCount}", exit.hasErrors)

compiler/test/dotty/tools/vulpix/TestConfiguration.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ object TestConfiguration {
3030
Properties.dottyLibrary
3131
))
3232

33+
val silenceOptions = Array(
34+
"-Wconf:id=E222:s", // name=EncodedPackageName don't warn about file names with hyphens
35+
)
36+
3337
val withCompilerClasspath = mkClasspath(List(
3438
Properties.scalaLibrary,
3539
Properties.scalaAsm,
@@ -63,7 +67,7 @@ object TestConfiguration {
6367

6468
val yCheckOptions = Array("-Ycheck:all")
6569

66-
val commonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions
70+
val commonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions ++ silenceOptions
6771
val defaultOptions = TestFlags(basicClasspath, commonOptions)
6872
val unindentOptions = TestFlags(basicClasspath, Array("-no-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions)
6973
val withCompilerOptions =

tests/neg/i22670.check

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- [E222] Syntax Error: tests/neg/i22670/i22670-macro.scala:4:8 --------------------------------------------------------
2+
4 |package xy // error named package required for warning
3+
|^
4+
|The package name `i22670-macro$package` will be encoded on the classpath, and can lead to undefined behaviour.
5+
5 |import scala.quoted.*
6+
6 |transparent inline def foo =
7+
7 | ${ fooImpl }
8+
8 |def fooImpl(using Quotes): Expr[Any] =
9+
9 | Expr("hello")
10+
|--------------------------------------------------------------------------------------------------------------------
11+
| Explanation (enabled by `-explain`)
12+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
13+
| Tooling may not cope with directories whose names do not match their package name.
14+
| For example, `p-q` is encoded as `p$minusq` and written that way to the file system.
15+
|
16+
| Package objects have names derived from their file names, so that names such as
17+
| `myfile.test.scala` and `myfile-test.scala` will result in encoded names for package objects.
18+
|
19+
| The name `i22670-macro$package` is encoded to `i22670$minusmacro$package`.
20+
--------------------------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)