From 5e714ddc0e9a83ef9502fe3ce3eec0f9e94f6369 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 21 Nov 2023 09:26:54 +0100 Subject: [PATCH 01/10] Add `CompilationUnitInfo` to `ClassSymbol` Use it to store the `associatedFile` allow for later expansion. --- .../tools/dotc/core/CompilationUnitInfo.scala | 21 ++++++ .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../dotty/tools/dotc/core/Denotations.scala | 3 +- .../src/dotty/tools/dotc/core/NamerOps.scala | 4 +- .../tools/dotc/core/SymDenotations.scala | 2 +- .../dotty/tools/dotc/core/SymbolLoaders.scala | 19 +++--- .../src/dotty/tools/dotc/core/Symbols.scala | 66 +++++++++++-------- .../core/unpickleScala2/Scala2Unpickler.scala | 2 +- .../transform/sjs/JUnitBootstrappers.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../scala/quoted/staging/QuoteCompiler.scala | 5 +- 11 files changed, 80 insertions(+), 48 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala diff --git a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala new file mode 100644 index 000000000000..d499bc5795ae --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala @@ -0,0 +1,21 @@ +package dotty.tools.dotc.core + +import dotty.tools.io.AbstractFile + +/** Information about the compilation unit of a class symbol. + * + * @param associatedFile The source or class file from which this class or + * the class containing this symbol was generated, + * null if not applicable. + */ +class CompilationUnitInfo( + val associatedFile: AbstractFile, +) { + override def toString(): String = + s"CompilationUnitInfo($associatedFile)" +} + +object CompilationUnitInfo: + def apply(assocFile: AbstractFile | Null): CompilationUnitInfo | Null = + if assocFile == null then null + else new CompilationUnitInfo(assocFile) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1a99c19baaf4..8c0132333a3f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1409,7 +1409,7 @@ class Definitions { ), privateWithin = patch.privateWithin, coord = denot.symbol.coord, - assocFile = denot.symbol.associatedFile + compUnitInfo = denot.symbol.compilationUnitInfo ) def makeNonClassSymbol(patch: Symbol) = diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 99156df9d86e..3608f16e3478 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -298,14 +298,13 @@ object Denotations { name: Name, site: Denotation = NoDenotation, args: List[Type] = Nil, - source: AbstractFile | Null = null, generateStubs: Boolean = true) (p: Symbol => Boolean) (using Context): Symbol = disambiguate(p) match { case m @ MissingRef(ownerd, name) if generateStubs => if ctx.settings.YdebugMissingRefs.value then m.ex.printStackTrace() - newStubSymbol(ownerd.symbol, name, source) + newStubSymbol(ownerd.symbol, name) case NoDenotation | _: NoQualifyingRef | _: MissingRef => def argStr = if (args.isEmpty) "" else i" matching ($args%, %)" val msg = diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index ea0cbfbd0c07..75a135826785 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -141,7 +141,7 @@ object NamerOps: ConstructorCompanionFlags, ConstructorCompanionFlags, constructorCompanionCompleter(cls), coord = cls.coord, - assocFile = cls.assocFile) + compUnitInfo = cls.compUnitInfo) companion.moduleClass.registerCompanion(cls) cls.registerCompanion(companion.moduleClass) companion @@ -150,7 +150,7 @@ object NamerOps: newSymbol(tsym.owner, tsym.name.toTermName, ConstructorCompanionFlags | StableRealizable | Method, ExprType(prefix.select(proxy)), coord = tsym.coord) - /** Add all necesssary constructor proxy symbols for members of class `cls`. This means: + /** Add all necessary constructor proxy symbols for members of class `cls`. This means: * * - if a member is a class, or type alias, that needs a constructor companion, add one, * provided no member with the same name exists. diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e18e1463f3ae..651bc4477347 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2588,7 +2588,7 @@ object SymDenotations { for (sym <- scope.toList.iterator) // We need to be careful to not force the denotation of `sym` here, // otherwise it will be brought forward to the current run. - if (sym.defRunId != ctx.runId && sym.isClass && sym.asClass.assocFile == file) + if (sym.defRunId != ctx.runId && sym.isClass && sym.asClass.compUnitInfo != null && sym.asClass.compUnitInfo.nn.associatedFile == file) scope.unlink(sym, sym.lastKnownDenotation.name) } } diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 5f6078a14625..7b3ba3e522c6 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -52,7 +52,7 @@ object SymbolLoaders { def enterClass( owner: Symbol, name: PreName, completer: SymbolLoader, flags: FlagSet = EmptyFlags, scope: Scope = EmptyScope)(using Context): Symbol = { - val cls = newClassSymbol(owner, name.toTypeName.unmangleClassName.decode, flags, completer, assocFile = completer.sourceFileOrNull) + val cls = newClassSymbol(owner, name.toTypeName.unmangleClassName.decode, flags, completer, compUnitInfo = completer.compilationUnitInfo) enterNew(owner, cls, completer, scope) } @@ -64,7 +64,7 @@ object SymbolLoaders { val module = newModuleSymbol( owner, name.toTermName.decode, modFlags, clsFlags, (module, _) => completer.proxy.withDecls(newScope).withSourceModule(module), - assocFile = completer.sourceFileOrNull) + compUnitInfo = completer.compilationUnitInfo) enterNew(owner, module, completer, scope) enterNew(owner, module.moduleClass, completer, scope) } @@ -213,7 +213,7 @@ object SymbolLoaders { /** Load contents of a package */ class PackageLoader(_sourceModule: TermSymbol, classPath: ClassPath) extends SymbolLoader { - override def sourceFileOrNull: AbstractFile | Null = null + def compilationUnitInfo: CompilationUnitInfo | Null = null override def sourceModule(using Context): TermSymbol = _sourceModule def description(using Context): String = "package loader " + sourceModule.fullName @@ -317,7 +317,7 @@ abstract class SymbolLoader extends LazyType { self => /** Load source or class file for `root`, return */ def doComplete(root: SymDenotation)(using Context): Unit - def sourceFileOrNull: AbstractFile | Null + def compilationUnitInfo: CompilationUnitInfo | Null /** Description of the resource (ClassPath, AbstractFile) * being processed by this loader @@ -328,7 +328,7 @@ abstract class SymbolLoader extends LazyType { self => * but provides fresh slots for scope/sourceModule/moduleClass */ def proxy: SymbolLoader = new SymbolLoader { - export self.{doComplete, sourceFileOrNull} + export self.{doComplete, compilationUnitInfo} def description(using Context): String = s"proxy to ${self.description}" } @@ -405,7 +405,8 @@ abstract class SymbolLoader extends LazyType { self => class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { - override def sourceFileOrNull: AbstractFile | Null = classfile + def compilationUnitInfo: CompilationUnitInfo | Null = CompilationUnitInfo(classfile) + def description(using Context): String = "class file " + classfile.toString @@ -417,7 +418,7 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { - override def sourceFileOrNull: AbstractFile | Null = tastyFile + def compilationUnitInfo: CompilationUnitInfo | Null = CompilationUnitInfo(tastyFile) def description(using Context): String = "TASTy file " + tastyFile.toString @@ -460,7 +461,7 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader { def description(using Context): String = "source file " + srcfile.toString - override def sourceFileOrNull: AbstractFile | Null = srcfile + def compilationUnitInfo: CompilationUnitInfo | Null = CompilationUnitInfo(srcfile) def doComplete(root: SymDenotation)(using Context): Unit = ctx.run.nn.lateCompile(srcfile, typeCheck = ctx.settings.YretainTrees.value) } @@ -468,7 +469,7 @@ class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader { /** A NoCompleter which is also a SymbolLoader. */ class NoLoader extends SymbolLoader with NoCompleter { def description(using Context): String = "NoLoader" - override def sourceFileOrNull: AbstractFile | Null = null + def compilationUnitInfo: CompilationUnitInfo | Null = null override def complete(root: SymDenotation)(using Context): Unit = super[NoCompleter].complete(root) def doComplete(root: SymDenotation)(using Context): Unit = diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index a41d194693e6..8c327e20d106 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -265,12 +265,21 @@ object Symbols { /** The source or class file from which this class or * the class containing this symbol was generated, null if not applicable. - * Note that this the returned classfile might be the top-level class + * Note that the returned classfile might be from the top-level class * containing this symbol instead of the directly enclosing class. - * Overridden in ClassSymbol */ def associatedFile(using Context): AbstractFile | Null = - lastDenot.topLevelClass.associatedFile + val compUnitInfo = compilationUnitInfo + if compUnitInfo == null then (null: AbstractFile | Null) + else compUnitInfo.associatedFile + + /** The compilation unit info (associated file, tasty versions, ...). + * Note that the returned CompilationUnitInfo might be from the top-level class + * containing this symbol instead of the directly enclosing class. + * Overridden in ClassSymbol + */ + def compilationUnitInfo(using Context): CompilationUnitInfo | Null = + lastDenot.topLevelClass.compilationUnitInfo /** The class file from which this class was generated, null if not applicable. */ final def binaryFile(using Context): AbstractFile | Null = { @@ -353,7 +362,7 @@ object Symbols { def paramRef(using Context): TypeRef = denot.typeRef /** Copy a symbol, overriding selective fields. - * Note that `coord` and `associatedFile` will be set from the fields in `owner`, not + * Note that `coord` and `compilationUnitInfo` will be set from the fields in `owner`, not * the fields in `sym`. */ def copy(using Context)( owner: Symbol = this.owner, @@ -362,13 +371,14 @@ object Symbols { info: Type = this.info, privateWithin: Symbol = this.privateWithin, coord: Coord = NoCoord, // Can be `= owner.coord` once we bootstrap - associatedFile: AbstractFile | Null = null // Can be `= owner.associatedFile` once we bootstrap + compUnitInfo: CompilationUnitInfo | Null = null // Can be `= owner.associatedFile` once we bootstrap ): Symbol = { val coord1 = if (coord == NoCoord) owner.coord else coord - val associatedFile1 = if (associatedFile == null) owner.associatedFile else associatedFile + val compilationUnitInfo1 = if (compilationUnitInfo == null) owner.compilationUnitInfo else compilationUnitInfo + if isClass then - newClassSymbol(owner, name.asTypeName, flags, _ => info, privateWithin, coord1, associatedFile1) + newClassSymbol(owner, name.asTypeName, flags, _ => info, privateWithin, coord1, compilationUnitInfo1) else newSymbol(owner, name, flags, info, privateWithin, coord1) } @@ -396,7 +406,7 @@ object Symbols { type TermSymbol = Symbol { type ThisName = TermName } type TypeSymbol = Symbol { type ThisName = TypeName } - class ClassSymbol private[Symbols] (coord: Coord, val assocFile: AbstractFile | Null, id: Int, nestingLevel: Int) + class ClassSymbol private[Symbols] (coord: Coord, val compUnitInfo: CompilationUnitInfo | Null, id: Int, nestingLevel: Int) extends Symbol(coord, id, nestingLevel) { type ThisName = TypeName @@ -456,9 +466,9 @@ object Symbols { } /** The source or class file from which this class was generated, null if not applicable. */ - override def associatedFile(using Context): AbstractFile | Null = - if assocFile != null || this.is(Package) || this.owner.is(Package) then assocFile - else super.associatedFile + override def compilationUnitInfo(using Context): CompilationUnitInfo | Null = + if compUnitInfo != null || this.is(Package) || this.owner.is(Package) then compUnitInfo + else super.compilationUnitInfo private var mySource: SourceFile = NoSource @@ -488,7 +498,7 @@ object Symbols { } @sharable object NoSymbol extends Symbol(NoCoord, 0, 0) { - override def associatedFile(using Context): AbstractFile | Null = NoSource.file + override def compilationUnitInfo(using Context): CompilationUnitInfo | Null = CompilationUnitInfo(NoSource.file) override def recomputeDenot(lastd: SymDenotation)(using Context): SymDenotation = NoDenotation } @@ -537,9 +547,9 @@ object Symbols { infoFn: ClassSymbol => Type, privateWithin: Symbol = NoSymbol, coord: Coord = NoCoord, - assocFile: AbstractFile | Null = null)(using Context): ClassSymbol + compUnitInfo: CompilationUnitInfo | Null = null)(using Context): ClassSymbol = { - val cls = new ClassSymbol(coord, assocFile, ctx.base.nextSymId, ctx.nestingLevel) + val cls = new ClassSymbol(coord, compUnitInfo, ctx.base.nextSymId, ctx.nestingLevel) val denot = SymDenotation(cls, owner, name, flags, infoFn(cls), privateWithin) cls.denot = denot cls @@ -555,11 +565,11 @@ object Symbols { selfInfo: Type = NoType, privateWithin: Symbol = NoSymbol, coord: Coord = NoCoord, - assocFile: AbstractFile | Null = null)(using Context): ClassSymbol = + compUnitInfo: CompilationUnitInfo | Null = null)(using Context): ClassSymbol = newClassSymbol( owner, name, flags, ClassInfo(owner.thisType, _, parents, decls, selfInfo), - privateWithin, coord, assocFile) + privateWithin, coord, compUnitInfo) /** Same as `newCompleteClassSymbol` except that `parents` can be a list of arbitrary * types which get normalized into type refs and parameter bindings. @@ -572,7 +582,7 @@ object Symbols { selfInfo: Type = NoType, privateWithin: Symbol = NoSymbol, coord: Coord = NoCoord, - assocFile: AbstractFile | Null = null)(using Context): ClassSymbol = { + compUnitInfo: CompilationUnitInfo | Null = null)(using Context): ClassSymbol = { def completer = new LazyType { def complete(denot: SymDenotation)(using Context): Unit = { val cls = denot.asClass.classSymbol @@ -580,7 +590,7 @@ object Symbols { denot.info = ClassInfo(owner.thisType, cls, parentTypes.map(_.dealias), decls, selfInfo) } } - newClassSymbol(owner, name, flags, completer, privateWithin, coord, assocFile) + newClassSymbol(owner, name, flags, completer, privateWithin, coord, compUnitInfo) } def newRefinedClassSymbol(coord: Coord = NoCoord)(using Context): ClassSymbol = @@ -598,7 +608,7 @@ object Symbols { infoFn: (TermSymbol, ClassSymbol) => Type, // typically a ModuleClassCompleterWithDecls privateWithin: Symbol = NoSymbol, coord: Coord = NoCoord, - assocFile: AbstractFile | Null = null)(using Context): TermSymbol + compUnitInfo: CompilationUnitInfo | Null = null)(using Context): TermSymbol = { val base = owner.thisType val modclsFlags = clsFlags | ModuleClassCreationFlags @@ -606,7 +616,7 @@ object Symbols { val module = newSymbol( owner, name, modFlags | ModuleValCreationFlags, NoCompleter, privateWithin, coord) val modcls = newClassSymbol( - owner, modclsName, modclsFlags, infoFn(module, _), privateWithin, coord, assocFile) + owner, modclsName, modclsFlags, infoFn(module, _), privateWithin, coord, compUnitInfo) module.info = if (modcls.isCompleted) TypeRef(owner.thisType, modcls) else new ModuleCompleter(modcls) @@ -627,12 +637,12 @@ object Symbols { decls: Scope, privateWithin: Symbol = NoSymbol, coord: Coord = NoCoord, - assocFile: AbstractFile | Null = null)(using Context): TermSymbol = + compUnitInfo: CompilationUnitInfo | Null = null)(using Context): TermSymbol = newModuleSymbol( owner, name, modFlags, clsFlags, (module, modcls) => ClassInfo( owner.thisType, modcls, parents, decls, TermRef(owner.thisType, module)), - privateWithin, coord, assocFile) + privateWithin, coord, compUnitInfo) /** Same as `newCompleteModuleSymbol` except that `parents` can be a list of arbitrary * types which get normalized into type refs and parameter bindings. @@ -646,7 +656,7 @@ object Symbols { decls: Scope, privateWithin: Symbol = NoSymbol, coord: Coord = NoCoord, - assocFile: AbstractFile | Null = null)(using Context): TermSymbol = { + compUnitInfo: CompilationUnitInfo | Null = null)(using Context): TermSymbol = { def completer(module: Symbol) = new LazyType { def complete(denot: SymDenotation)(using Context): Unit = { val cls = denot.asClass.classSymbol @@ -657,7 +667,7 @@ object Symbols { newModuleSymbol( owner, name, modFlags, clsFlags, (module, modcls) => completer(module), - privateWithin, coord, assocFile) + privateWithin, coord, compUnitInfo) } /** Create a package symbol with associated package class @@ -697,17 +707,17 @@ object Symbols { /** Create a stub symbol that will issue a missing reference error * when attempted to be completed. */ - def newStubSymbol(owner: Symbol, name: Name, file: AbstractFile | Null = null)(using Context): Symbol = { + def newStubSymbol(owner: Symbol, name: Name, compUnitInfo: CompilationUnitInfo | Null = null)(using Context): Symbol = { def stubCompleter = new StubInfo() val normalizedOwner = if (owner.is(ModuleVal)) owner.moduleClass else owner - typr.println(s"creating stub for ${name.show}, owner = ${normalizedOwner.denot.debugString}, file = $file") + typr.println(s"creating stub for ${name.show}, owner = ${normalizedOwner.denot.debugString}, compilation unit = $compUnitInfo") typr.println(s"decls = ${normalizedOwner.unforcedDecls.toList.map(_.debugString).mkString("\n ")}") // !!! DEBUG //if (base.settings.debug.value) throw new Error() val stub = name match { case name: TermName => - newModuleSymbol(normalizedOwner, name, EmptyFlags, EmptyFlags, stubCompleter, assocFile = file) + newModuleSymbol(normalizedOwner, name, EmptyFlags, EmptyFlags, stubCompleter, compUnitInfo = compUnitInfo) case name: TypeName => - newClassSymbol(normalizedOwner, name, EmptyFlags, stubCompleter, assocFile = file) + newClassSymbol(normalizedOwner, name, EmptyFlags, stubCompleter, compUnitInfo = compUnitInfo) } stub } diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index b5ee0e0525b4..8e9bfb6802a4 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -426,7 +426,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas if (slowSearch(name).exists) System.err.println(i"**** slow search found: ${slowSearch(name)}") if (ctx.settings.YdebugMissingRefs.value) Thread.dumpStack() - newStubSymbol(owner, name, source) + newStubSymbol(owner, name, CompilationUnitInfo(source)) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/JUnitBootstrappers.scala b/compiler/src/dotty/tools/dotc/transform/sjs/JUnitBootstrappers.scala index 7655eb79d6d4..b7a179ac7562 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/JUnitBootstrappers.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/JUnitBootstrappers.scala @@ -156,7 +156,7 @@ class JUnitBootstrappers extends MiniPhase { val moduleSym = newCompleteModuleSymbol(owner, bootstrapperName, Synthetic, Synthetic, List(defn.ObjectType, junitdefn.BootstrapperType), newScope, - coord = testClass.span, assocFile = testClass.assocFile).entered + coord = testClass.span, compUnitInfo = testClass.compUnitInfo).entered val classSym = moduleSym.moduleClass.asClass val constr = genConstructor(classSym) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index cca26abdd1ec..7735104bc8ee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -250,7 +250,7 @@ class Namer { typer: Typer => val cls = createOrRefine[ClassSymbol](tree, name, flags, ctx.owner, cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree), - newClassSymbol(ctx.owner, name, _, _, _, tree.nameSpan, ctx.source.file)) + newClassSymbol(ctx.owner, name, _, _, _, tree.nameSpan, CompilationUnitInfo(ctx.source.file))) cls.completer.asInstanceOf[ClassCompleter].init() cls case tree: MemberDef => diff --git a/staging/src/scala/quoted/staging/QuoteCompiler.scala b/staging/src/scala/quoted/staging/QuoteCompiler.scala index ce20055493fa..d10eab321bae 100644 --- a/staging/src/scala/quoted/staging/QuoteCompiler.scala +++ b/staging/src/scala/quoted/staging/QuoteCompiler.scala @@ -5,6 +5,7 @@ import dotty.tools.unsupported import dotty.tools.dotc._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.CompilationUnitInfo import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.Mode @@ -69,12 +70,12 @@ private class QuoteCompiler extends Compiler: implicit val unitCtx: Context = SpliceScope.setSpliceScope(new RunScope)(using ctx1) val pos = Span(0) - val assocFile = new VirtualFile("") + val compUnitInfo = CompilationUnitInfo(new VirtualFile("")) // Places the contents of expr in a compilable tree for a class with the following format. // `package __root__ { class ' { def apply: Any = } }` val cls = newCompleteClassSymbol(defn.RootClass, outputClassName, EmptyFlags, - defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass + defn.ObjectType :: Nil, newScope, coord = pos, compUnitInfo = compUnitInfo).entered.asClass cls.enter(newDefaultConstructor(cls), EmptyScope) val meth = newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered From 83c05b6ad52f9438a4d2104b9e4c471a65e9992f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 21 Nov 2023 13:28:23 +0100 Subject: [PATCH 02/10] Keep `TastyVersion` in `CompilationUnitInfo` --- .../tools/dotc/core/CompilationUnitInfo.scala | 16 +++++++++++-- .../dotty/tools/dotc/core/SymbolLoaders.scala | 8 ++++++- .../dotc/core/tasty/DottyUnpickler.scala | 8 +++++++ .../dotc/core/tasty/TastyUnpickler.scala | 5 ++-- .../core/tasty/TastyHeaderUnpicklerTest.scala | 2 +- .../tools/tasty/TastyHeaderUnpickler.scala | 24 ------------------- .../src/dotty/tools/tasty/TastyVersion.scala | 24 +++++++++++++++++++ 7 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 tasty/src/dotty/tools/tasty/TastyVersion.scala diff --git a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala index d499bc5795ae..0c4367b3c7e0 100644 --- a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala +++ b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala @@ -1,21 +1,33 @@ package dotty.tools.dotc.core import dotty.tools.io.AbstractFile +import dotty.tools.tasty.TastyVersion /** Information about the compilation unit of a class symbol. * * @param associatedFile The source or class file from which this class or * the class containing this symbol was generated, * null if not applicable. + * @param tastyVersion The TASTy version (major, minor, experimental) */ class CompilationUnitInfo( val associatedFile: AbstractFile, + private var tastyVersionOpt: Option[TastyVersion], ) { + + def tastyVersion: Option[TastyVersion] = tastyVersionOpt + + /** Sets the TASTy version. Used to initialize the TASTy version when + * Loading a TASTy file in TastyLoader. + */ + def initTastyVersion(version: TastyVersion): Unit = + tastyVersionOpt = Some(version) + override def toString(): String = - s"CompilationUnitInfo($associatedFile)" + s"CompilationUnitInfo($associatedFile, $tastyVersion)" } object CompilationUnitInfo: def apply(assocFile: AbstractFile | Null): CompilationUnitInfo | Null = if assocFile == null then null - else new CompilationUnitInfo(assocFile) + else new CompilationUnitInfo(assocFile, tastyVersionOpt = None) // TODO use current TASTy version diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 7b3ba3e522c6..997ef013e09f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -418,7 +418,12 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { - def compilationUnitInfo: CompilationUnitInfo | Null = CompilationUnitInfo(tastyFile) + private val compUnitInfo = new CompilationUnitInfo( + tastyFile, + tastyVersionOpt = None // set on doComplete + ) + + def compilationUnitInfo: CompilationUnitInfo | Null = compUnitInfo def description(using Context): String = "TASTy file " + tastyFile.toString @@ -427,6 +432,7 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { val (classRoot, moduleRoot) = rootDenots(root.asClass) val tastyBytes = tastyFile.toByteArray val unpickler = new tasty.DottyUnpickler(tastyBytes) + compUnitInfo.initTastyVersion(unpickler.tastyVersion) unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource)) if mayLoadTreesFromTasty then classRoot.classSymbol.rootTreeOrProvider = unpickler diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index 84e70cc91663..8268d8e3e843 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -14,6 +14,7 @@ import TreeUnpickler.UnpickleMode import dotty.tools.tasty.TastyReader import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection, AttributesSection} +import dotty.tools.tasty.TastyVersion object DottyUnpickler { @@ -55,6 +56,13 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe private val attributeUnpicklerOpt = unpickler.unpickle(new AttributesSectionUnpickler) private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt)).get + def tastyVersion: TastyVersion = + TastyVersion( + unpickler.header.majorVersion, + unpickler.header.minorVersion, + unpickler.header.experimentalVersion, + ) + /** Enter all toplevel classes and objects into their scopes * @param roots a set of SymDenotations that should be overwritten by unpickling */ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala index 3a6f1e02a705..f3ec4a603172 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala @@ -4,8 +4,7 @@ package tasty import scala.language.unsafeNulls -import dotty.tools.tasty.{TastyFormat, TastyBuffer, TastyReader, TastyHeaderUnpickler, UnpicklerConfig} -import TastyHeaderUnpickler.TastyVersion +import dotty.tools.tasty.{TastyFormat, TastyVersion, TastyBuffer, TastyReader, TastyHeaderUnpickler, UnpicklerConfig} import TastyFormat.NameTags.*, TastyFormat.nameTagToString import TastyBuffer.NameRef @@ -122,7 +121,7 @@ class TastyUnpickler(reader: TastyReader) { result } - new TastyHeaderUnpickler(scala3CompilerConfig, reader).readHeader() + val header = new TastyHeaderUnpickler(scala3CompilerConfig, reader).readFullHeader() locally { until(readEnd()) { nameAtRef.add(readNameContents()) } diff --git a/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala index 53c1f40638a4..bde4246ef0f0 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala @@ -9,7 +9,7 @@ import dotty.tools.tasty.TastyBuffer import dotty.tools.tasty.TastyReader import dotty.tools.tasty.UnpickleException import dotty.tools.tasty.TastyHeaderUnpickler -import dotty.tools.tasty.TastyHeaderUnpickler.TastyVersion +import dotty.tools.tasty.TastyVersion import dotty.tools.tasty.UnpicklerConfig class TastyHeaderUnpicklerTest { diff --git a/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala b/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala index db07666d3be1..a51541192321 100644 --- a/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala +++ b/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala @@ -3,7 +3,6 @@ package dotty.tools.tasty import java.util.UUID import TastyFormat.{MajorVersion, MinorVersion, ExperimentalVersion, header} -import TastyHeaderUnpickler.TastyVersion /** * The Tasty Header consists of four fields: @@ -214,27 +213,4 @@ object TastyHeaderUnpickler { BackwardIncompatibleExperimental else ForwardIncompatible } - - case class TastyVersion(major: Int, minor: Int, experimental: Int) { - def isExperimental: Boolean = experimental > 0 - - def nextStable: TastyVersion = copy(experimental = 0) - - def minStable: TastyVersion = copy(minor = 0, experimental = 0) - - def show: String = { - val suffix = if (isExperimental) s"-experimental-$experimental" else "" - s"$major.$minor$suffix" - } - - def kind: String = - if (isExperimental) "experimental TASTy" else "TASTy" - - def validRange: String = { - val min = TastyVersion(major, 0, 0) - val max = if (experimental == 0) this else TastyVersion(major, minor - 1, 0) - val extra = Option.when(experimental > 0)(this) - s"stable TASTy from ${min.show} to ${max.show}${extra.fold("")(e => s", or exactly ${e.show}")}" - } - } } diff --git a/tasty/src/dotty/tools/tasty/TastyVersion.scala b/tasty/src/dotty/tools/tasty/TastyVersion.scala new file mode 100644 index 000000000000..0d2f1b8c76d8 --- /dev/null +++ b/tasty/src/dotty/tools/tasty/TastyVersion.scala @@ -0,0 +1,24 @@ +package dotty.tools.tasty + +case class TastyVersion(major: Int, minor: Int, experimental: Int) { + def isExperimental: Boolean = experimental > 0 + + def nextStable: TastyVersion = copy(experimental = 0) + + def minStable: TastyVersion = copy(minor = 0, experimental = 0) + + def show: String = { + val suffix = if (isExperimental) s"-experimental-$experimental" else "" + s"$major.$minor$suffix" + } + + def kind: String = + if (isExperimental) "experimental TASTy" else "TASTy" + + def validRange: String = { + val min = TastyVersion(major, 0, 0) + val max = if (experimental == 0) this else TastyVersion(major, minor - 1, 0) + val extra = Option.when(experimental > 0)(this) + s"stable TASTy from ${min.show} to ${max.show}${extra.fold("")(e => s", or exactly ${e.show}")}" + } +} \ No newline at end of file From ff0b15374230c07b6b6b5f18d46e420efb6053d8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 16 Nov 2023 09:00:17 +0100 Subject: [PATCH 03/10] Allow `infix enum` Fixes #18933 --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 +++- tests/neg/i18933.check | 10 ++++++++++ tests/neg/i18933.scala | 3 +++ tests/pos/i18933.scala | 4 ++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i18933.check create mode 100644 tests/neg/i18933.scala create mode 100644 tests/pos/i18933.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index a2cc6499e843..19f75506b182 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3922,7 +3922,9 @@ object Parsers { } private def checkAccessOnly(mods: Modifiers, where: String): Modifiers = - val mods1 = mods & (AccessFlags | Enum) + // We allow `infix to mark the `enum`s type as infix. + // Syntax rules disallow the soft infix modifier on `case`s. + val mods1 = mods & (AccessFlags | Enum | Infix) if mods1 ne mods then syntaxError(em"Only access modifiers are allowed on enum $where") mods1 diff --git a/tests/neg/i18933.check b/tests/neg/i18933.check new file mode 100644 index 000000000000..dc4db455c2de --- /dev/null +++ b/tests/neg/i18933.check @@ -0,0 +1,10 @@ +-- Error: tests/neg/i18933.scala:3:8 ----------------------------------------------------------------------------------- +3 | infix case B(b: B) // error // error + | ^^^^ + | end of statement expected but 'case' found +-- [E006] Not Found Error: tests/neg/i18933.scala:3:2 ------------------------------------------------------------------ +3 | infix case B(b: B) // error // error + | ^^^^^ + | Not found: infix + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i18933.scala b/tests/neg/i18933.scala new file mode 100644 index 000000000000..7802c3961a25 --- /dev/null +++ b/tests/neg/i18933.scala @@ -0,0 +1,3 @@ +enum Extends[A, B]: + case A(a: A) + infix case B(b: B) // error // error diff --git a/tests/pos/i18933.scala b/tests/pos/i18933.scala new file mode 100644 index 000000000000..7424344d5edf --- /dev/null +++ b/tests/pos/i18933.scala @@ -0,0 +1,4 @@ +//> using options -Werror + +infix enum Extends[A, B]: + case Ev[B, A <: B]() extends (A Extends B) From 8b44ffa0f8951dd9550e631fd2ac66ebde5e1b18 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 14 Nov 2023 16:45:44 +0100 Subject: [PATCH 04/10] Deprecation warnings for old syntax: alphanumeric infix operators This is the first part of #18870 --- compiler/src/dotty/tools/dotc/core/Symbols.scala | 7 +++++++ .../src/dotty/tools/dotc/typer/Checking.scala | 14 ++++++++++---- compiler/test-resources/repl/i1374 | 2 +- compiler/test-resources/type-printer/infix | 2 +- .../languageserver/util/embedded/CodeMarker.scala | 2 +- scaladoc-testcases/src/tests/infixTypes.scala | 4 ++-- .../scaladoc/tasty/comments/MemberLookup.scala | 4 ++-- .../scaladoc/tasty/comments/wiki/Parser.scala | 4 ++-- .../test/dotty/tools/scaladoc/testUtils.scala | 2 +- tests/init-global/pos/i18628-lazy.scala | 4 ++-- tests/init-global/pos/i18628.scala | 4 ++-- tests/init/pos/Properties.scala | 12 ++++++------ tests/init/pos/i15465.scala | 2 +- tests/neg/alphanumeric-infix-operator-3.4.scala | 11 +++++++++++ tests/neg/alphanumeric-infix-operator.check | 6 ++++++ tests/neg/alphanumeric-infix-operator.scala | 9 +++++++++ tests/neg/i10901.scala | 8 ++++---- tests/neg/i2033.check | 6 ++++++ tests/neg/rewrite-messages.check | 2 +- tests/neg/syntax-error-recovery.check | 6 ++++++ .../A_1_c3.0.0.scala | 3 +++ .../B_1_c3.1.0.scala | 3 +++ .../C_1_c3.2.0.scala | 3 +++ .../D_1_c3.3.0.scala | 3 +++ .../Test3.4_2.scala | 15 +++++++++++++++ .../TestFuture_2.scala | 13 +++++++++++++ tests/pos/i7424c.scala | 2 +- tests/semanticdb/metac.expect | 6 ++++++ 28 files changed, 128 insertions(+), 31 deletions(-) create mode 100644 tests/neg/alphanumeric-infix-operator-3.4.scala create mode 100644 tests/neg/alphanumeric-infix-operator.check create mode 100644 tests/neg/alphanumeric-infix-operator.scala create mode 100644 tests/pos/alphanumeric-infix-operator-compat/A_1_c3.0.0.scala create mode 100644 tests/pos/alphanumeric-infix-operator-compat/B_1_c3.1.0.scala create mode 100644 tests/pos/alphanumeric-infix-operator-compat/C_1_c3.2.0.scala create mode 100644 tests/pos/alphanumeric-infix-operator-compat/D_1_c3.3.0.scala create mode 100644 tests/pos/alphanumeric-infix-operator-compat/Test3.4_2.scala create mode 100644 tests/pos/alphanumeric-infix-operator-compat/TestFuture_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 8c327e20d106..ccc65289f511 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -34,6 +34,7 @@ import config.Printers.typr import dotty.tools.dotc.classpath.FileUtils.isScalaBinary import scala.compiletime.uninitialized +import dotty.tools.tasty.TastyVersion object Symbols { @@ -281,6 +282,12 @@ object Symbols { def compilationUnitInfo(using Context): CompilationUnitInfo | Null = lastDenot.topLevelClass.compilationUnitInfo + /** The version of TASTy from which the symbol was loaded, None if not applicable. */ + def tastyVersion(using Context): Option[TastyVersion] = + val compUnitInfo = compilationUnitInfo + if compUnitInfo == null then None + else compUnitInfo.tastyVersion + /** The class file from which this class was generated, null if not applicable. */ final def binaryFile(using Context): AbstractFile | Null = { val file = associatedFile diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 90c26e279d01..76b6a1804e7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1071,12 +1071,17 @@ trait Checking { def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(using Context): Unit = { tree.op match { case id @ Ident(name: Name) => + def methCompiledBeforeDeprecation = + meth.tastyVersion match + case Some(version) => version.minor < 4 // compiled before 3.4 + case _ => false // compiled with the current compiler name.toTermName match { case name: SimpleName if !untpd.isBackquoted(id) && !name.isOperatorName && !meth.isDeclaredInfix && !meth.maybeOwner.is(Scala2x) && + !methCompiledBeforeDeprecation && !infixOKSinceFollowedBy(tree.right) => val (kind, alternative) = if (ctx.mode.is(Mode.Type)) @@ -1085,13 +1090,14 @@ trait Checking { ("extractor", (n: Name) => s"prefix syntax $n(...)") else ("method", (n: Name) => s"method syntax .$n(...)") - def rewriteMsg = Message.rewriteNotice("The latter", version = `future-migration`) - report.errorOrMigrationWarning( + def rewriteMsg = Message.rewriteNotice("The latter", version = `3.4-migration`) + report.gradualErrorOrMigrationWarning( em"""Alphanumeric $kind $name is not declared ${hlAsKeyword("infix")}; it should not be used as infix operator. |Instead, use ${alternative(name)} or backticked identifier `$name`.$rewriteMsg""", tree.op.srcPos, - from = future) - if sourceVersion == `future-migration` then { + warnFrom = `3.4`, + errorFrom = future) + if sourceVersion.isMigrating && sourceVersion.isAtLeast(`3.4-migration`) then { patch(Span(tree.op.span.start, tree.op.span.start), "`") patch(Span(tree.op.span.end, tree.op.span.end), "`") } diff --git a/compiler/test-resources/repl/i1374 b/compiler/test-resources/repl/i1374 index 3d117fdb4ff9..2e0b5be900af 100644 --- a/compiler/test-resources/repl/i1374 +++ b/compiler/test-resources/repl/i1374 @@ -1,4 +1,4 @@ -scala> implicit class Padder(val sb: StringBuilder) extends AnyVal { def pad2(width: Int) = { 1 to width - sb.length foreach { sb append '*' }; sb } } +scala> implicit class Padder(val sb: StringBuilder) extends AnyVal { infix def pad2(width: Int) = { 1 to width - sb.length foreach { sb append '*' }; sb } } // defined class Padder def Padder(sb: StringBuilder): Padder scala> val greeting = new StringBuilder("Hello, kitteh!") diff --git a/compiler/test-resources/type-printer/infix b/compiler/test-resources/type-printer/infix index 2fe2864ad9fe..bedb7071e7f2 100644 --- a/compiler/test-resources/type-printer/infix +++ b/compiler/test-resources/type-printer/infix @@ -29,7 +29,7 @@ def foo: (Int &: String) & Boolean scala> def foo: Int &: (Boolean & String) = ??? def foo: Int &: (Boolean & String) scala> import scala.annotation.showAsInfix -scala> @scala.annotation.showAsInfix class Mappy[T,U] +scala> @scala.annotation.showAsInfix infix class Mappy[T,U] // defined class Mappy scala> def foo: (Int Mappy Boolean) && String = ??? def foo: (Int Mappy Boolean) && String diff --git a/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala b/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala index 416730dc0f4e..33e6a875750a 100644 --- a/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala +++ b/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala @@ -13,7 +13,7 @@ import PositionContext.PosCtx class CodeMarker(val name: String) extends Embedded { /** A range of positions between this marker and `other`. */ - def to(other: CodeMarker): CodeRange = CodeRange(this, other) + infix def to(other: CodeMarker): CodeRange = CodeRange(this, other) /** The file containing this marker. */ def file: PosCtx[TestFile] = posCtx.positionOf(this)._1 diff --git a/scaladoc-testcases/src/tests/infixTypes.scala b/scaladoc-testcases/src/tests/infixTypes.scala index acfc9044e2eb..30fc982f2bca 100644 --- a/scaladoc-testcases/src/tests/infixTypes.scala +++ b/scaladoc-testcases/src/tests/infixTypes.scala @@ -4,9 +4,9 @@ package infixTypes import annotation.showAsInfix @showAsInfix -trait SomeTrait[A, B] +infix trait SomeTrait[A, B] -trait SomeTrait2[A, B] +infix trait SomeTrait2[A, B] def someTrait1[C, D]: C SomeTrait D = ??? diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala index b2c4e1bdcac4..26c4fb06dfdf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala @@ -236,9 +236,9 @@ object MemberLookup extends MemberLookup { // Scaladoc overloading support allows terminal * (and they're meaningless) val cleanStr = str.stripSuffix("*") - if cleanStr endsWith "$" then + if cleanStr.endsWith("$") then Selector(cleanStr.init, SelectorKind.ForceTerm) - else if cleanStr endsWith "!" then + else if cleanStr.endsWith("!") then Selector(cleanStr.init, SelectorKind.ForceType) else Selector(cleanStr, SelectorKind.NoForce) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala index dd3187fb5346..125bca102fba 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala @@ -675,7 +675,7 @@ sealed class CharReader(buffer: String) { reader => var offset: Int = 0 def char: Char = - if (offset >= buffer.length) endOfText else buffer charAt offset + if (offset >= buffer.length) endOfText else buffer.charAt(offset) final def nextChar() = offset += 1 @@ -712,7 +712,7 @@ sealed class CharReader(buffer: String) { reader => jumpWhitespace() val (ok0, chars0) = if (chars.charAt(0) == ' ') - (offset > poff, chars substring 1) + (offset > poff, chars.substring(1)) else (true, chars) val ok = ok0 && jump(chars0) diff --git a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala index 2ba78c321eab..d8b0e179c5f1 100644 --- a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala +++ b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala @@ -71,7 +71,7 @@ def tastyFiles(name: String, allowEmpty: Boolean = false, rootPck: String = "tes } def collectFiles(dir: File): List[File] = listFilesSafe(dir).toList.flatMap { case f if f.isDirectory => collectFiles(f) - case f if f.getName endsWith ".tasty" => f :: Nil + case f if f.getName.endsWith(".tasty") => f :: Nil case _ => Nil } val outputDir = BuildInfo.test_testcasesOutputDir diff --git a/tests/init-global/pos/i18628-lazy.scala b/tests/init-global/pos/i18628-lazy.scala index f93745a962be..16e194e9fc37 100644 --- a/tests/init-global/pos/i18628-lazy.scala +++ b/tests/init-global/pos/i18628-lazy.scala @@ -55,10 +55,10 @@ trait Parsers { } def flatMap[U](f: T => Parser[U]): Parser[U] - = Parser{ in => this(in) flatMapWithNext(f)} + = Parser{ in => this(in).flatMapWithNext(f)} def map[U](f: T => U): Parser[U] //= flatMap{x => success(f(x))} - = Parser{ in => this(in) map(f)} + = Parser{ in => this(in).map(f)} def ^^ [U](f: T => U): Parser[U] = map(f) } diff --git a/tests/init-global/pos/i18628.scala b/tests/init-global/pos/i18628.scala index 71d9a1ed30a8..e9fd8f359356 100644 --- a/tests/init-global/pos/i18628.scala +++ b/tests/init-global/pos/i18628.scala @@ -55,10 +55,10 @@ trait Parsers { } def flatMap[U](f: T => Parser[U]): Parser[U] - = Parser{ in => this(in) flatMapWithNext(f)} + = Parser{ in => this(in).flatMapWithNext(f)} def map[U](f: T => U): Parser[U] //= flatMap{x => success(f(x))} - = Parser{ in => this(in) map(f)} + = Parser{ in => this(in).map(f)} def ^^ [U](f: T => U): Parser[U] = map(f) } diff --git a/tests/init/pos/Properties.scala b/tests/init/pos/Properties.scala index ba92dd54dc83..8dd2fa052c0b 100644 --- a/tests/init/pos/Properties.scala +++ b/tests/init/pos/Properties.scala @@ -23,9 +23,9 @@ private[scala] trait PropertiesTrait { /** The loaded properties */ protected lazy val scalaProps: java.util.Properties = { val props = new java.util.Properties - val stream = pickJarBasedOn getResourceAsStream propFilename + val stream = pickJarBasedOn.getResourceAsStream(propFilename) if (stream ne null) - quietlyDispose(props load stream, stream.close) + quietlyDispose(props.load(stream), stream.close) props } @@ -47,8 +47,8 @@ private[scala] trait PropertiesTrait { final def setProp(name: String, value: String) = System.setProperty(name, value) final def clearProp(name: String) = System.clearProperty(name) - final def envOrElse(name: String, alt: String) = Option(System getenv name) getOrElse alt - final def envOrNone(name: String) = Option(System getenv name) + final def envOrElse(name: String, alt: String) = Option(System.getenv(name)) getOrElse alt + final def envOrNone(name: String) = Option(System.getenv(name)) final def envOrSome(name: String, alt: Option[String]) = envOrNone(name) orElse alt @@ -68,7 +68,7 @@ private[scala] trait PropertiesTrait { val releaseVersion = for { v <- scalaPropOrNone("maven.version.number") - if !(v endsWith "-SNAPSHOT") + if !(v.endsWith("-SNAPSHOT")) } yield v /** The development Scala version, if this is not a final release. @@ -82,7 +82,7 @@ private[scala] trait PropertiesTrait { val developmentVersion = for { v <- scalaPropOrNone("maven.version.number") - if v endsWith "-SNAPSHOT" + if v.endsWith("-SNAPSHOT") ov <- scalaPropOrNone("version.number") } yield ov diff --git a/tests/init/pos/i15465.scala b/tests/init/pos/i15465.scala index 5b99670e9027..1b0c9e1fead3 100644 --- a/tests/init/pos/i15465.scala +++ b/tests/init/pos/i15465.scala @@ -2,7 +2,7 @@ class TestSuite: protected val it = new ItWord protected final class ItWord: - def should(string: String) = new ItVerbString("should", string) + infix def should(string: String) = new ItVerbString("should", string) private def registerTestToRun(fun: => Any): Unit = () diff --git a/tests/neg/alphanumeric-infix-operator-3.4.scala b/tests/neg/alphanumeric-infix-operator-3.4.scala new file mode 100644 index 000000000000..14d3358127ca --- /dev/null +++ b/tests/neg/alphanumeric-infix-operator-3.4.scala @@ -0,0 +1,11 @@ +//> using options -Werror + +import language.`3.4` + +class Foo: + def x(i: Int) = i + infix def y(i: Int) = i + +def test(foo: Foo): Unit = + foo x 1 // error (because it was compiled with 3.4+) + foo y 2 // ok: is marked as infix diff --git a/tests/neg/alphanumeric-infix-operator.check b/tests/neg/alphanumeric-infix-operator.check new file mode 100644 index 000000000000..52b08f16b88c --- /dev/null +++ b/tests/neg/alphanumeric-infix-operator.check @@ -0,0 +1,6 @@ +-- Error: tests/neg/alphanumeric-infix-operator.scala:8:6 -------------------------------------------------------------- +8 | foo x 1 // error (because it was compiled with 3.4+) + | ^ + | Alphanumeric method x is not declared infix; it should not be used as infix operator. + | Instead, use method syntax .x(...) or backticked identifier `x`. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/neg/alphanumeric-infix-operator.scala b/tests/neg/alphanumeric-infix-operator.scala new file mode 100644 index 000000000000..1f2233dda6ce --- /dev/null +++ b/tests/neg/alphanumeric-infix-operator.scala @@ -0,0 +1,9 @@ +//> using options -Werror + +class Foo: + def x(i: Int) = i + infix def y(i: Int) = i + +def test(foo: Foo): Unit = + foo x 1 // error (because it was compiled with 3.4+) + foo y 2 // ok: is marked as infix diff --git a/tests/neg/i10901.scala b/tests/neg/i10901.scala index 19a2e3023c85..dc1ea6e6eef6 100644 --- a/tests/neg/i10901.scala +++ b/tests/neg/i10901.scala @@ -13,23 +13,23 @@ object BugExp4Point2D { // N - N @targetName("point2DConstant") - def º(y: T2): Point2D[T1,T2] = ??? + infix def º(y: T2): Point2D[T1,T2] = ??? // N - C @targetName("point2DConstantData") - def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? + infix def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? extension [T1:Numeric, T2:Numeric](x: ColumnType[T1]) // C - C @targetName("point2DData") - def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? + infix def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? // C - N @targetName("point2DDataConstant") - def º(y: T2): Point2D[T1,T2] = ??? + infix def º(y: T2): Point2D[T1,T2] = ??? } diff --git a/tests/neg/i2033.check b/tests/neg/i2033.check index 5751d91f4f3a..7737bba96a5e 100644 --- a/tests/neg/i2033.check +++ b/tests/neg/i2033.check @@ -13,3 +13,9 @@ 6 | val out = new ObjectOutputStream(println) | ^^^^^^^ |method println is eta-expanded even though java.io.OutputStream does not have the @FunctionalInterface annotation. +-- Warning: tests/neg/i2033.scala:7:18 --------------------------------------------------------------------------------- +7 | val arr = bos toByteArray () // error + | ^^^^^^^^^^^ + | Alphanumeric method toByteArray is not declared infix; it should not be used as infix operator. + | Instead, use method syntax .toByteArray(...) or backticked identifier `toByteArray`. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/neg/rewrite-messages.check b/tests/neg/rewrite-messages.check index f368c2dc8997..b062ab2bc732 100644 --- a/tests/neg/rewrite-messages.check +++ b/tests/neg/rewrite-messages.check @@ -8,4 +8,4 @@ | ^^^ | Alphanumeric method foo is not declared infix; it should not be used as infix operator. | Instead, use method syntax .foo(...) or backticked identifier `foo`. - | The latter can be rewritten automatically under -rewrite -source future-migration. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/neg/syntax-error-recovery.check b/tests/neg/syntax-error-recovery.check index 0cf550c74d09..d93a939b0c72 100644 --- a/tests/neg/syntax-error-recovery.check +++ b/tests/neg/syntax-error-recovery.check @@ -136,3 +136,9 @@ | Discarded non-Unit value of type Null. You may want to use `()`. | | longer explanation available when compiling with `-explain` +-- Warning: tests/neg/syntax-error-recovery.scala:61:2 ----------------------------------------------------------------- +61 | println(bam) + | ^^^^^^^ + | Alphanumeric method println is not declared infix; it should not be used as infix operator. + | Instead, use method syntax .println(...) or backticked identifier `println`. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/pos/alphanumeric-infix-operator-compat/A_1_c3.0.0.scala b/tests/pos/alphanumeric-infix-operator-compat/A_1_c3.0.0.scala new file mode 100644 index 000000000000..51ea7ec45de6 --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/A_1_c3.0.0.scala @@ -0,0 +1,3 @@ +class A: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/B_1_c3.1.0.scala b/tests/pos/alphanumeric-infix-operator-compat/B_1_c3.1.0.scala new file mode 100644 index 000000000000..2377b01f7fec --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/B_1_c3.1.0.scala @@ -0,0 +1,3 @@ +class B: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/C_1_c3.2.0.scala b/tests/pos/alphanumeric-infix-operator-compat/C_1_c3.2.0.scala new file mode 100644 index 000000000000..c1e11d2547ee --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/C_1_c3.2.0.scala @@ -0,0 +1,3 @@ +class C: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/D_1_c3.3.0.scala b/tests/pos/alphanumeric-infix-operator-compat/D_1_c3.3.0.scala new file mode 100644 index 000000000000..0e05a819694c --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/D_1_c3.3.0.scala @@ -0,0 +1,3 @@ +class D: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/Test3.4_2.scala b/tests/pos/alphanumeric-infix-operator-compat/Test3.4_2.scala new file mode 100644 index 000000000000..dd6367518fb4 --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/Test3.4_2.scala @@ -0,0 +1,15 @@ +//> using options -Werror + +import language.`3.4` + +def test1(a: A, b: B, c: C, d: D): Unit = + a x 1 // ok: was compiled with 3.0 + b x 1 // ok: was compiled with 3.1 + c x 1 // ok: was compiled with 3.2 + d x 1 // ok: was compiled with 3.3 + + // ok: is marked as infix + a y 2 + b y 2 + c y 2 + d y 2 diff --git a/tests/pos/alphanumeric-infix-operator-compat/TestFuture_2.scala b/tests/pos/alphanumeric-infix-operator-compat/TestFuture_2.scala new file mode 100644 index 000000000000..cf6a9d0adfc2 --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/TestFuture_2.scala @@ -0,0 +1,13 @@ +import language.future + +def test2(a: A, b: B, c: C, d: D): Unit = + a x 1 // ok: was compiled with 3.0 + b x 1 // ok: was compiled with 3.1 + c x 1 // ok: was compiled with 3.2 + d x 1 // ok: was compiled with 3.3 + + // ok: is marked as infix + a y 2 + b y 2 + c y 2 + d y 2 diff --git a/tests/pos/i7424c.scala b/tests/pos/i7424c.scala index 9a02328f82a2..b40ef00a48dd 100644 --- a/tests/pos/i7424c.scala +++ b/tests/pos/i7424c.scala @@ -1,6 +1,6 @@ //> using options -Werror object Main extends App: - enum Extends[A, B]: + infix enum Extends[A, B]: case Ev[B, A <: B]() extends (A Extends B) def cast(a: A): B = this match { diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 717bfb0505fb..2fd8eca47a7b 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3460,6 +3460,7 @@ Text => empty Language => Scala Symbols => 12 entries Occurrences => 33 entries +Diagnostics => 1 entries Synthetics => 4 entries Symbols: @@ -3511,6 +3512,11 @@ Occurrences: [20:4..20:13): scalameta -> scala/reflect/Selectable#selectDynamic(). [21:4..21:19): StructuralTypes -> example/StructuralTypes. +Diagnostics: +[14:20..14:23): [warning] Alphanumeric method foo is not declared infix; it should not be used as infix operator. +Instead, use method syntax .foo(...) or backticked identifier `foo`. +The latter can be rewritten automatically under -rewrite -source 3.4-migration. + Synthetics: [12:2..12:6):user => reflectiveSelectable(*) [13:2..13:6):user => reflectiveSelectable(*) From 0291fa8a67c205bda7bf003f972aec62c4b86a53 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 21 Nov 2023 15:21:56 +0100 Subject: [PATCH 05/10] Read TASTy header eagerly to avoid var in CompilationUnitInfo Also avoid reading header twice to check the UUID. --- .../tools/dotc/core/CompilationUnitInfo.scala | 12 +---- .../dotty/tools/dotc/core/SymbolLoaders.scala | 45 +++++++++++-------- .../dotc/core/tasty/DottyUnpickler.scala | 7 --- 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala index 0c4367b3c7e0..ffad60c81f72 100644 --- a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala +++ b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala @@ -12,17 +12,9 @@ import dotty.tools.tasty.TastyVersion */ class CompilationUnitInfo( val associatedFile: AbstractFile, - private var tastyVersionOpt: Option[TastyVersion], + val tastyVersion: Option[TastyVersion], ) { - def tastyVersion: Option[TastyVersion] = tastyVersionOpt - - /** Sets the TASTy version. Used to initialize the TASTy version when - * Loading a TASTy file in TastyLoader. - */ - def initTastyVersion(version: TastyVersion): Unit = - tastyVersionOpt = Some(version) - override def toString(): String = s"CompilationUnitInfo($associatedFile, $tastyVersion)" } @@ -30,4 +22,4 @@ class CompilationUnitInfo( object CompilationUnitInfo: def apply(assocFile: AbstractFile | Null): CompilationUnitInfo | Null = if assocFile == null then null - else new CompilationUnitInfo(assocFile, tastyVersionOpt = None) // TODO use current TASTy version + else new CompilationUnitInfo(assocFile, tastyVersion = None) // TODO use current TASTy version diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 997ef013e09f..c5a88b45aa55 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -24,10 +24,9 @@ import ast.desugar import parsing.JavaParsers.OutlineJavaParser import parsing.Parsers.OutlineParser -import dotty.tools.tasty.{TastyHeaderUnpickler, UnpickleException, UnpicklerConfig} +import dotty.tools.tasty.{TastyHeaderUnpickler, UnpickleException, UnpicklerConfig, TastyVersion} import dotty.tools.dotc.core.tasty.TastyUnpickler - object SymbolLoaders { import ast.untpd.* @@ -418,44 +417,54 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { - private val compUnitInfo = new CompilationUnitInfo( - tastyFile, - tastyVersionOpt = None // set on doComplete + private val unpickler: tasty.DottyUnpickler = + handleUnpicklingExceptions: + val tastyBytes = tastyFile.toByteArray + new tasty.DottyUnpickler(tastyBytes) // reads header and name table + + def compilationUnitInfo: CompilationUnitInfo | Null = + val tastyHeader = unpickler.unpickler.header + new CompilationUnitInfo( + tastyFile, + tastyVersion = Some( + TastyVersion( + tastyHeader.majorVersion, + tastyHeader.minorVersion, + tastyHeader.experimentalVersion, + ) + ) ) - def compilationUnitInfo: CompilationUnitInfo | Null = compUnitInfo - def description(using Context): String = "TASTy file " + tastyFile.toString override def doComplete(root: SymDenotation)(using Context): Unit = - try + handleUnpicklingExceptions: + checkTastyUUID() val (classRoot, moduleRoot) = rootDenots(root.asClass) - val tastyBytes = tastyFile.toByteArray - val unpickler = new tasty.DottyUnpickler(tastyBytes) - compUnitInfo.initTastyVersion(unpickler.tastyVersion) unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource)) if mayLoadTreesFromTasty then classRoot.classSymbol.rootTreeOrProvider = unpickler moduleRoot.classSymbol.rootTreeOrProvider = unpickler - checkTastyUUID(tastyFile, tastyBytes) + + private def handleUnpicklingExceptions[T](thunk: =>T): T = + try thunk catch case e: RuntimeException => val message = e match case e: UnpickleException => - i"""TASTy file ${tastyFile.canonicalPath} could not be read, failing with: + s"""TASTy file ${tastyFile.canonicalPath} could not be read, failing with: | ${Option(e.getMessage).getOrElse("")}""" case _ => - i"""TASTy file ${tastyFile.canonicalPath} is broken, reading aborted with ${e.getClass} + s"""TASTy file ${tastyFile.canonicalPath} is broken, reading aborted with ${e.getClass} | ${Option(e.getMessage).getOrElse("")}""" - if (ctx.debug) e.printStackTrace() - throw IOException(message) + throw IOException(message, e) - private def checkTastyUUID(tastyFile: AbstractFile, tastyBytes: Array[Byte])(using Context): Unit = + private def checkTastyUUID()(using Context): Unit = val classfile = val className = tastyFile.name.stripSuffix(".tasty") tastyFile.resolveSibling(className + ".class") if classfile != null then - val tastyUUID = new TastyHeaderUnpickler(TastyUnpickler.scala3CompilerConfig, tastyBytes).readHeader() + val tastyUUID = unpickler.unpickler.header.uuid new ClassfileTastyUUIDParser(classfile)(ctx).checkTastyUUID(tastyUUID) else // This will be the case in any of our tests that compile with `-Youtput-only-tasty` diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index 8268d8e3e843..239293372981 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -56,13 +56,6 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe private val attributeUnpicklerOpt = unpickler.unpickle(new AttributesSectionUnpickler) private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt)).get - def tastyVersion: TastyVersion = - TastyVersion( - unpickler.header.majorVersion, - unpickler.header.minorVersion, - unpickler.header.experimentalVersion, - ) - /** Enter all toplevel classes and objects into their scopes * @param roots a set of SymDenotations that should be overwritten by unpickling */ From 91f8e3acb5d2492990be751a670a4914f6f827c1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 21 Nov 2023 15:50:47 +0100 Subject: [PATCH 06/10] Add explicit nulls tasty attribute to CompilationUnit --- .../tools/dotc/core/CompilationUnitInfo.scala | 8 +++++++- .../dotty/tools/dotc/core/SymbolLoaders.scala | 17 +++++++++-------- .../tools/dotc/core/tasty/DottyUnpickler.scala | 3 +++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala index ffad60c81f72..954aca953ab7 100644 --- a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala +++ b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala @@ -9,10 +9,12 @@ import dotty.tools.tasty.TastyVersion * the class containing this symbol was generated, * null if not applicable. * @param tastyVersion The TASTy version (major, minor, experimental) + * @param tastyExplicitNulls Was this compilation unit compiled with explicit nulls? */ class CompilationUnitInfo( val associatedFile: AbstractFile, val tastyVersion: Option[TastyVersion], + val tastyExplicitNulls: Boolean ) { override def toString(): String = @@ -22,4 +24,8 @@ class CompilationUnitInfo( object CompilationUnitInfo: def apply(assocFile: AbstractFile | Null): CompilationUnitInfo | Null = if assocFile == null then null - else new CompilationUnitInfo(assocFile, tastyVersion = None) // TODO use current TASTy version + else new CompilationUnitInfo( + assocFile, + tastyVersion = None, + tastyExplicitNulls = false // TODO track explicit nulls for current compilation units (not only TASTy) + ) diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index c5a88b45aa55..4bc970915d68 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -424,16 +424,17 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { def compilationUnitInfo: CompilationUnitInfo | Null = val tastyHeader = unpickler.unpickler.header + val tastyVersion = TastyVersion( + tastyHeader.majorVersion, + tastyHeader.minorVersion, + tastyHeader.experimentalVersion, + ) + val attributes = unpickler.tastyAttributes new CompilationUnitInfo( tastyFile, - tastyVersion = Some( - TastyVersion( - tastyHeader.majorVersion, - tastyHeader.minorVersion, - tastyHeader.experimentalVersion, - ) - ) - ) + tastyVersion = Some(tastyVersion), + tastyExplicitNulls = attributes.explicitNulls, + ) def description(using Context): String = "TASTy file " + tastyFile.toString diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index 239293372981..02e3fc8c04e4 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -56,6 +56,9 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe private val attributeUnpicklerOpt = unpickler.unpickle(new AttributesSectionUnpickler) private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt)).get + def tastyAttributes: Attributes = + attributeUnpicklerOpt.map(_.attributes).getOrElse(Attributes(false, false)) + /** Enter all toplevel classes and objects into their scopes * @param roots a set of SymDenotations that should be overwritten by unpickling */ From 2f36da43ca100c68da6edbf297485ad44d98703f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 22 Nov 2023 08:10:05 +0100 Subject: [PATCH 07/10] Set explicit nulls in CompilationUnitInfo symbols from source --- .../src/dotty/tools/dotc/core/CompilationUnitInfo.scala | 8 ++++---- compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala | 2 +- compiler/src/dotty/tools/dotc/core/Symbols.scala | 5 +++++ compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala index 954aca953ab7..50b1d0bc5fcd 100644 --- a/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala +++ b/compiler/src/dotty/tools/dotc/core/CompilationUnitInfo.scala @@ -9,12 +9,12 @@ import dotty.tools.tasty.TastyVersion * the class containing this symbol was generated, * null if not applicable. * @param tastyVersion The TASTy version (major, minor, experimental) - * @param tastyExplicitNulls Was this compilation unit compiled with explicit nulls? + * @param explicitNulls This compilation unit has explicit nulls enabled? */ class CompilationUnitInfo( val associatedFile: AbstractFile, val tastyVersion: Option[TastyVersion], - val tastyExplicitNulls: Boolean + val explicitNulls: Boolean ) { override def toString(): String = @@ -22,10 +22,10 @@ class CompilationUnitInfo( } object CompilationUnitInfo: - def apply(assocFile: AbstractFile | Null): CompilationUnitInfo | Null = + def apply(assocFile: AbstractFile | Null, explicitNulls: Boolean = false): CompilationUnitInfo | Null = if assocFile == null then null else new CompilationUnitInfo( assocFile, tastyVersion = None, - tastyExplicitNulls = false // TODO track explicit nulls for current compilation units (not only TASTy) + explicitNulls = explicitNulls, ) diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 4bc970915d68..a4a9b973dc62 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -433,7 +433,7 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { new CompilationUnitInfo( tastyFile, tastyVersion = Some(tastyVersion), - tastyExplicitNulls = attributes.explicitNulls, + explicitNulls = attributes.explicitNulls, ) def description(using Context): String = "TASTy file " + tastyFile.toString diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index ccc65289f511..7a67e676ae1a 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -288,6 +288,11 @@ object Symbols { if compUnitInfo == null then None else compUnitInfo.tastyVersion + /** If this class has explicit nulls enabled */ + def explicitNulls(using Context): Boolean = + val compUnitInfo = compilationUnitInfo + compUnitInfo != null && compUnitInfo.explicitNulls + /** The class file from which this class was generated, null if not applicable. */ final def binaryFile(using Context): AbstractFile | Null = { val file = associatedFile diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 7735104bc8ee..357f0d7b41e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -250,7 +250,7 @@ class Namer { typer: Typer => val cls = createOrRefine[ClassSymbol](tree, name, flags, ctx.owner, cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree), - newClassSymbol(ctx.owner, name, _, _, _, tree.nameSpan, CompilationUnitInfo(ctx.source.file))) + newClassSymbol(ctx.owner, name, _, _, _, tree.nameSpan, CompilationUnitInfo(ctx.source.file, explicitNulls = ctx.explicitNulls))) cls.completer.asInstanceOf[ClassCompleter].init() cls case tree: MemberDef => From e152b56eb2cb35ebb08d5a4a19f2ba71bff27f06 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 22 Nov 2023 15:30:33 +0100 Subject: [PATCH 08/10] Review feedback - Test major TASTy version - strip margins of `s".."` - Cache `CompilationUnit` in `SymbolLoaders` --- compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala | 6 +++--- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index a4a9b973dc62..a156d2a911e4 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -422,7 +422,7 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { val tastyBytes = tastyFile.toByteArray new tasty.DottyUnpickler(tastyBytes) // reads header and name table - def compilationUnitInfo: CompilationUnitInfo | Null = + val compilationUnitInfo: CompilationUnitInfo | Null = val tastyHeader = unpickler.unpickler.header val tastyVersion = TastyVersion( tastyHeader.majorVersion, @@ -453,10 +453,10 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { val message = e match case e: UnpickleException => s"""TASTy file ${tastyFile.canonicalPath} could not be read, failing with: - | ${Option(e.getMessage).getOrElse("")}""" + | ${Option(e.getMessage).getOrElse("")}""".stripMargin case _ => s"""TASTy file ${tastyFile.canonicalPath} is broken, reading aborted with ${e.getClass} - | ${Option(e.getMessage).getOrElse("")}""" + | ${Option(e.getMessage).getOrElse("")}""".stripMargin throw IOException(message, e) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 76b6a1804e7f..04db7f086eec 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1073,7 +1073,7 @@ trait Checking { case id @ Ident(name: Name) => def methCompiledBeforeDeprecation = meth.tastyVersion match - case Some(version) => version.minor < 4 // compiled before 3.4 + case Some(version) => version.major == 28 && version.minor < 4 // compiled before 3.4 case _ => false // compiled with the current compiler name.toTermName match { case name: SimpleName From 97d81712ff46f42e077b717fc1723b13c678f215 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 22 Nov 2023 15:55:21 +0100 Subject: [PATCH 09/10] Cache stable TASTyVersion Co-authored-by: Nicolas Stucki Co-authored-by: Jamie Thompson --- .../src/dotty/tools/tasty/TastyVersion.scala | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tasty/src/dotty/tools/tasty/TastyVersion.scala b/tasty/src/dotty/tools/tasty/TastyVersion.scala index 0d2f1b8c76d8..b6474f7c7934 100644 --- a/tasty/src/dotty/tools/tasty/TastyVersion.scala +++ b/tasty/src/dotty/tools/tasty/TastyVersion.scala @@ -1,6 +1,8 @@ package dotty.tools.tasty -case class TastyVersion(major: Int, minor: Int, experimental: Int) { +import scala.annotation.internal.sharable + +case class TastyVersion private(major: Int, minor: Int, experimental: Int) { def isExperimental: Boolean = experimental > 0 def nextStable: TastyVersion = copy(experimental = 0) @@ -21,4 +23,17 @@ case class TastyVersion(major: Int, minor: Int, experimental: Int) { val extra = Option.when(experimental > 0)(this) s"stable TASTy from ${min.show} to ${max.show}${extra.fold("")(e => s", or exactly ${e.show}")}" } -} \ No newline at end of file +} + +object TastyVersion { + + @sharable + private val cache: java.util.concurrent.ConcurrentHashMap[TastyVersion, TastyVersion] = + new java.util.concurrent.ConcurrentHashMap() + + def apply(major: Int, minor: Int, experimental: Int): TastyVersion = { + val version = new TastyVersion(major, minor, experimental) + val cachedVersion = cache.putIfAbsent(version, version) + if (cachedVersion == null) version else cachedVersion + } +} From 050859e1df458a0c3aeed7d34438b157fb3ab341 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 23 Nov 2023 12:15:33 +0100 Subject: [PATCH 10/10] add scala3 library to tasty-core-scala2 project --- project/Build.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 2cf9966daf2a..10ce82469fa6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2094,7 +2094,16 @@ object Build { } ) - def asTastyCoreScala2: Project = project.settings(commonScala2Settings) + def asTastyCoreScala2: Project = project + .settings(commonScala2Settings) + // need to add @annotation.internal.sharable to the classpath for compiling + // we don't actually publish this library anywhere, so it's fine. + // if someone depends on the sources of tasty-core in a scala 2 project, + // they should strip the sharable annotation, or add -Ytasty-reader + .dependsOn(dottyLibrary(NonBootstrapped) % Provided) + .settings( + scalacOptions += "-Ytasty-reader" // to read scala3 library + ) def asDottyBench(implicit mode: Mode): Project = project.withCommonSettings. dependsOn(dottyCompiler).