Skip to content

Commit 361a4f3

Browse files
committed
Warn about encoded pkg obj names
1 parent d8ddcfb commit 361a4f3

File tree

17 files changed

+123
-32
lines changed

17 files changed

+123
-32
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, FileExtension}
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}
@@ -87,10 +88,12 @@ class Driver {
8788
if !ctx.settings.XdropComments.value || ctx.settings.XreadComments.value then
8889
ictx.setProperty(ContextDoc, new ContextDocstrings)
8990
val fileNamesOrNone = command.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)
90-
fileNamesOrNone.map { fileNames =>
91+
fileNamesOrNone.map: fileNames =>
9192
val files = fileNames.map(ctx.getFile)
9293
(files, fromTastySetup(files))
93-
}
94+
.tap: _ =>
95+
if ctx.settings.YnoReporter.value then
96+
ictx.setReporter(Reporter.SilentReporter())
9497
}
9598
}
9699

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

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,25 +1267,27 @@ object desugar {
12671267
else tree
12681268
}
12691269

1270-
def checkPackageName(mdef: ModuleDef | PackageDef)(using Context): Unit =
1271-
1272-
def check(name: Name, errSpan: Span): Unit = name match
1273-
case name: SimpleName if !errSpan.isSynthetic && name.exists(Chars.willBeEncoded) =>
1274-
report.warning(em"The package name `$name` will be encoded on the classpath, and can lead to undefined behaviour.", mdef.source.atSpan(errSpan))
1275-
case _ =>
1276-
1277-
def loop(part: RefTree): Unit = part match
1278-
case part @ Ident(name) => check(name, part.span)
1279-
case part @ Select(qual: RefTree, name) =>
1280-
check(name, part.nameSpan)
1281-
loop(qual)
1270+
def checkSimplePackageName(name: Name, errSpan: Span, source: SourceFile, isPackageObject: Boolean)(using Context) =
1271+
if !ctx.isAfterTyper then
1272+
name match
1273+
case name: SimpleName if (isPackageObject || !errSpan.isSynthetic) && name.exists(Chars.willBeEncoded) =>
1274+
report.warning(EncodedPackageName(name), source.atSpan(errSpan))
12821275
case _ =>
12831276

1277+
def checkPackageName(mdef: ModuleDef | PackageDef)(using Context): Unit =
1278+
def check(name: Name, errSpan: Span) = checkSimplePackageName(name, errSpan, mdef.source, isPackageObject = false)
12841279
mdef match
1285-
case pdef: PackageDef => loop(pdef.pid)
1286-
case mdef: ModuleDef if mdef.mods.is(Package) => check(mdef.name, mdef.nameSpan)
1287-
case _ =>
1288-
end checkPackageName
1280+
case pdef: PackageDef =>
1281+
def loop(part: RefTree): Unit = part match
1282+
case part @ Ident(name) => check(name, part.span)
1283+
case part @ Select(qual: RefTree, name) =>
1284+
check(name, part.nameSpan)
1285+
loop(qual)
1286+
case _ =>
1287+
loop(pdef.pid)
1288+
case mdef: ModuleDef if mdef.mods.is(Package) =>
1289+
check(mdef.name, mdef.nameSpan)
1290+
case _ =>
12891291

12901292
/** The normalized name of `mdef`. This means
12911293
* 1. Check that the name does not redefine a Scala core class.
@@ -1856,7 +1858,7 @@ object desugar {
18561858
/** Assuming `src` contains top-level definition, returns the name that should
18571859
* be using for the package object that will wrap them.
18581860
*/
1859-
def packageObjectName(src: SourceFile): TermName =
1861+
def packageObjectName(src: SourceFile, srcPos: SrcPos)(using Context): TermName =
18601862
val fileName = src.file.name
18611863
val sourceName = fileName.take(fileName.lastIndexOf('.'))
18621864
(sourceName ++ str.TOPLEVEL_SUFFIX).toTermName
@@ -1887,7 +1889,7 @@ object desugar {
18871889
val (nestedStats, topStats) = pdef.stats.partition(inPackageObject)
18881890
if (nestedStats.isEmpty) pdef
18891891
else {
1890-
val name = packageObjectName(ctx.source)
1892+
val name = packageObjectName(ctx.source, pdef.srcPos)
18911893
val grouped =
18921894
ModuleDef(name, Template(emptyConstructor, Nil, Nil, EmptyValDef, nestedStats))
18931895
.withMods(Modifiers(Synthetic))

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,8 @@ private sealed trait YSettings:
397397
val YdisableFlatCpCaching: Setting[Boolean] = BooleanSetting(ForkSetting, "YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
398398
val YnoStdlibPatches: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-stdlib-patches", "Do not patch stdlib files (temporary and only to be used for the stdlib migration)", false)
399399

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

402404
val YnoImports: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.")

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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3749,3 +3749,15 @@ final class RecurseWithDefault(name: Name)(using Context) extends TypeMsg(Recurs
37493749
i"Recursive call used a default argument for parameter $name."
37503750
override protected def explain(using Context): String =
37513751
"It's more explicit to pass current or modified arguments in a recursion."
3752+
3753+
final class EncodedPackageName(name: Name)(using Context) extends SyntaxMsg(EncodedPackageNameID):
3754+
override protected def msg(using Context): String =
3755+
i"The package name `$name` will be encoded on the classpath, and can lead to undefined behaviour."
3756+
override protected def explain(using Context): String =
3757+
i"""Tooling may not cope with directories whose names do not match their package name.
3758+
|For example, `p-q` is encoded as `p$$minusq` and written that way to the file system.
3759+
|
3760+
|Package objects have names derived from their file names, so that names such as
3761+
|`myfile.test.scala` and `myfile-test.scala` will result in encoded names for package objects.
3762+
|
3763+
|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}
@@ -3476,12 +3477,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34763477
inContext(ctx.packageContext(tree, pkg)) {
34773478
// If it exists, complete the class containing the top-level definitions
34783479
// before typing any statement in the package to avoid cycles as in i13669.scala
3479-
val topLevelClassName = desugar.packageObjectName(ctx.source).moduleClassName
3480-
pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted()
3480+
val packageObjectName = desugar.packageObjectName(ctx.source, tree.srcPos)
3481+
val topLevelClassSymbol = pkg.moduleClass.info.decls.lookup(packageObjectName.moduleClassName)
3482+
topLevelClassSymbol.ensureCompleted()
34813483
var stats1 = typedStats(tree.stats, pkg.moduleClass)._1
34823484
if (!ctx.isAfterTyper)
34833485
stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1
34843486
cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef)
3487+
.tap: _ =>
3488+
if !ctx.isAfterTyper
3489+
&& pkg != defn.EmptyPackageVal
3490+
&& !topLevelClassSymbol.info.decls.filter(sym => !sym.isConstructor && !sym.is(Synthetic)).isEmpty
3491+
then
3492+
desugar.checkSimplePackageName(packageObjectName, tree.span, ctx.source, isPackageObject = true)
34853493
}
34863494
case _ =>
34873495
// Package will not exist if a duplicate type has already been entered, see `tests/neg/1708.scala`
@@ -3882,8 +3890,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
38823890
def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(using Context): (List[Tree], Context) = {
38833891
val buf = new mutable.ListBuffer[Tree]
38843892
var enumContexts: SimpleIdentityMap[Symbol, Context] = SimpleIdentityMap.empty
3885-
val initialNotNullInfos = ctx.notNullInfos
38863893
// A map from `enum` symbols to the contexts enclosing their definitions
3894+
val initialNotNullInfos = ctx.notNullInfos
38873895
@tailrec def traverse(stats: List[untpd.Tree])(using Context): (List[Tree], Context) = stats match {
38883896
case (imp: untpd.Import) :: rest =>
38893897
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
@@ -25,6 +25,10 @@ object TestConfiguration {
2525
"-Xverify-signatures"
2626
)
2727

28+
val silenceOptions = Array(
29+
"-Wconf:id=E222:s", // name=EncodedPackageName don't warn about file names with hyphens
30+
)
31+
2832
val basicClasspath = mkClasspath(List(Properties.scalaLibrary))
2933

3034
val withCompilerClasspath = mkClasspath(List(
@@ -59,7 +63,7 @@ object TestConfiguration {
5963

6064
val yCheckOptions = Array("-Ycheck:all")
6165

62-
val commonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions
66+
val commonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions ++ silenceOptions
6367
val noYcheckCommonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions
6468
val defaultOptions = TestFlags(basicClasspath, commonOptions) `and` "-Yno-stdlib-patches"
6569
val noYcheckOptions = TestFlags(basicClasspath, noYcheckCommonOptions)

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)