From 09b774623a49864ac34ea0213495e3555b82920b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Feb 2021 15:35:58 +0100 Subject: [PATCH 1/6] Make main methods invisible Add a new flag Invisible that gets set for compiler-generated main methods. Invisible symbols are skipped when resolving members during typechecking. They become visible after typechecking. With that mechanism, we can accept main methods that are themselves called `main`. --- compiler/src/dotty/tools/dotc/ast/MainProxies.scala | 10 +--------- compiler/src/dotty/tools/dotc/core/Denotations.scala | 3 ++- compiler/src/dotty/tools/dotc/core/Flags.scala | 7 +++++-- .../src/dotty/tools/dotc/core/SymDenotations.scala | 9 +++++---- .../src/dotty/tools/dotc/core/tasty/TreePickler.scala | 1 + .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 1 + compiler/test/dotty/tools/dotc/CompilationTests.scala | 3 ++- tasty/src/dotty/tools/tasty/TastyFormat.scala | 8 ++++++-- tests/neg-custom-args/fatal-warnings/i10137.scala | 4 ---- tests/pos/main.scala | 4 ++++ 10 files changed, 27 insertions(+), 23 deletions(-) delete mode 100644 tests/neg-custom-args/fatal-warnings/i10137.scala create mode 100644 tests/pos/main.scala diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 45364481a319..9178478c399b 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -39,13 +39,6 @@ object MainProxies { mainMethods(stats).flatMap(mainProxy) } - private def checkNoShadowing(mainFun: Symbol)(using Context) = - val cls = ctx.typer.findRef(mainFun.name.toTypeName, WildcardType, EmptyFlags, EmptyFlags, mainFun).typeSymbol - if cls.exists && cls.owner != ctx.owner then - report.warning( - i"""The class `${ctx.printer.fullNameString(mainFun)}` generated from `@main` will shadow the existing ${cls.showLocated}. - |The existing definition might no longer be found on recompile.""", mainFun) - import untpd._ def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { val mainAnnotSpan = mainFun.getAnnotation(defn.MainAnnot).get.tree.span @@ -93,7 +86,6 @@ object MainProxies { case _ => report.error(s"@main can only annotate a method", pos) } - checkNoShadowing(mainFun) val errVar = Ident(nme.error) val handler = CaseDef( Typed(errVar, TypeTree(defn.CLP_ParseError.typeRef)), @@ -106,7 +98,7 @@ object MainProxies { .withFlags(JavaStatic) val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) - .withFlags(Final) + .withFlags(Final | Invisible) if (!ctx.reporter.hasErrors) result = mainCls.withSpan(mainAnnotSpan.toSynthetic) :: Nil } result diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 24aa48213f31..80b2d6f13718 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -1061,11 +1061,12 @@ object Denotations { def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = if (denots.exists && denots.matches(this)) NoDenotation else this def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): SingleDenotation = + val realExcluded = if ctx.isAfterTyper then excluded else excluded | Invisible def symd: SymDenotation = this match case symd: SymDenotation => symd case _ => symbol.denot if !required.isEmpty && !symd.isAllOf(required) - || !excluded.isEmpty && symd.isOneOf(excluded) then NoDenotation + || symd.isOneOf(realExcluded) then NoDenotation else this def aggregate[T](f: SingleDenotation => T, g: (T, T) => T): T = f(this) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 8aeb68cbe984..5bcc32de158f 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -369,6 +369,9 @@ object Flags { /** An infix method or type */ val (Infix @ _, _, _) = newFlags(44, "infix") + /** Symbol cannot be found as a member during typer */ + val (Invisible @ _, _, _) = newFlags(45, "") + // ------------ Flags following this one are not pickled ---------------------------------- /** Symbol is not a member of its owner */ @@ -458,7 +461,7 @@ object Flags { Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic, OuterOrCovariant, LabelOrContravariant, CaseAccessor, Extension, NonMember, Implicit, Given, Permanent, Synthetic, - SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy) + SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy, Invisible) /** Flags that are not (re)set when completing the denotation, or, if symbol is * a top-level class or object, when completing the denotation once the class @@ -512,7 +515,7 @@ object Flags { val RetainedModuleValAndClassFlags: FlagSet = AccessFlags | Package | Case | Synthetic | JavaDefined | JavaStatic | Artifact | - Lifted | MixedIn | Specialized | ConstructorProxy + Lifted | MixedIn | Specialized | ConstructorProxy | Invisible /** Flags that can apply to a module val */ val RetainedModuleValFlags: FlagSet = RetainedModuleValAndClassFlags | diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index fee5f1f0be87..38b3dc174a72 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -572,8 +572,9 @@ object SymDenotations { isAbsent(canForce) case _ => // Otherwise, no completion is necessary, see the preconditions of `markAbsent()`. - (myInfo `eq` NoType) || - is(ModuleVal, butNot = Package) && moduleClass.isAbsent(canForce) + (myInfo `eq` NoType) + || is(Invisible) && !ctx.isAfterTyper + || is(ModuleVal, butNot = Package) && moduleClass.isAbsent(canForce) } /** Is this symbol the root class or its companion object? */ @@ -2209,8 +2210,8 @@ object SymDenotations { ensureCompleted() myCompanion - override def registeredCompanion_=(c: Symbol) = - myCompanion = c + override def registeredCompanion_=(c: Symbol) = + myCompanion = c private var myNestingLevel = -1 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 10af44c76169..43cb9bda3b29 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -724,6 +724,7 @@ class TreePickler(pickler: TastyPickler) { if (flags.is(Artifact)) writeModTag(ARTIFACT) if flags.is(Transparent) then writeModTag(TRANSPARENT) if flags.is(Infix) then writeModTag(INFIX) + if flags.is(Invisible) then writeModTag(INVISIBLE) if (isTerm) { if (flags.is(Implicit)) writeModTag(IMPLICIT) if (flags.is(Given)) writeModTag(GIVEN) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index d2b9b607a391..98100402a489 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -670,6 +670,7 @@ class TreeUnpickler(reader: TastyReader, case PARAMalias => addFlag(SuperParamAlias) case EXPORTED => addFlag(Exported) case OPEN => addFlag(Open) + case INVISIBLE => addFlag(Invisible) case TRANSPARENT => addFlag(Transparent) case INFIX => addFlag(Infix) case PRIVATEqualified => diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 53fd04c04136..989efadb9239 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -109,7 +109,8 @@ class CompilationTests { compileFile("tests/pos/i0239.scala", defaultOptions), compileFile("tests/pos/anonClassSubtyping.scala", defaultOptions), compileFile("tests/pos/extmethods.scala", defaultOptions), - compileFile("tests/pos/companions.scala", defaultOptions) + compileFile("tests/pos/companions.scala", defaultOptions), + compileFile("tests/pos/main.scala", defaultOptions) ).times(2).checkCompile() } diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index f5969169353a..ce3b642ea5e2 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -211,7 +211,8 @@ Standard-Section: "ASTs" TopLevelStat* PARAMsetter -- The setter part `x_=` of a var parameter `x` which itself is pickled as a PARAM PARAMalias -- Parameter is alias of a superclass parameter EXPORTED -- An export forwarder - OPEN -- an open class + OPEN + INVISIBLE -- an open class Annotation Variance = STABLE -- invariant @@ -284,7 +285,7 @@ object TastyFormat { * compatibility, but remains backwards compatible, with all * preceeding `MinorVersion`. */ - final val MinorVersion: Int = 0 + final val MinorVersion: Int = 1 /**Natural Number. The `ExperimentalVersion` allows for * experimentation with changes to TASTy without committing @@ -474,6 +475,7 @@ object TastyFormat { final val INFIX = 43 final val EMPTYCLAUSE = 44 final val SPLITCLAUSE = 45 + final val INVISIBLE = 46 // Cat. 2: tag Nat @@ -636,6 +638,7 @@ object TastyFormat { | PARAMalias | EXPORTED | OPEN + | INVISIBLE | ANNOTATION | PRIVATEqualified | PROTECTEDqualified => true @@ -698,6 +701,7 @@ object TastyFormat { case PARAMsetter => "PARAMsetter" case EXPORTED => "EXPORTED" case OPEN => "OPEN" + case INVISIBLE => "INVISIBLE" case PARAMalias => "PARAMalias" case EMPTYCLAUSE => "EMPTYCLAUSE" case SPLITCLAUSE => "SPLITCLAUSE" diff --git a/tests/neg-custom-args/fatal-warnings/i10137.scala b/tests/neg-custom-args/fatal-warnings/i10137.scala deleted file mode 100644 index 3272c3434e07..000000000000 --- a/tests/neg-custom-args/fatal-warnings/i10137.scala +++ /dev/null @@ -1,4 +0,0 @@ -package foo: - @main def main(): Unit = println("Hello, World!") // error - -@main def List(): Unit = println("List") // error diff --git a/tests/pos/main.scala b/tests/pos/main.scala new file mode 100644 index 000000000000..4f3fd4681e47 --- /dev/null +++ b/tests/pos/main.scala @@ -0,0 +1,4 @@ +package foo: + @main def main(): Unit = println("Hello, World!") + +@main def List(): Unit = println("List") From 789ceb1f36ceb1e412a09f3bec9912ad6c6066ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Feb 2021 16:09:28 +0100 Subject: [PATCH 2/6] Make bean properties invisible Load bean properties in TreeUnpickler but make them invisible to Typer. This looks like the safer choice. --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 +--- compiler/src/dotty/tools/dotc/transform/BeanProperties.scala | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 98100402a489..526573921b44 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -585,11 +585,9 @@ class TreeUnpickler(reader: TastyReader, val annots = annotFns.map(_(sym.owner)) sym.annotations = annots if sym.isOpaqueAlias then sym.setFlag(Deferred) - val isSyntheticBeanAccessor = flags.isAllOf(Method | Synthetic) && - annots.exists(a => a.matches(defn.BeanPropertyAnnot) || a.matches(defn.BooleanBeanPropertyAnnot)) val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased) ctx.owner match { - case cls: ClassSymbol if (!isScala2MacroDefinedInScala3 || cls == defn.StringContextClass) && !isSyntheticBeanAccessor => + case cls: ClassSymbol if !isScala2MacroDefinedInScala3 || cls == defn.StringContextClass => // Enter all members of classes that are not Scala 2 macros or synthetic bean accessors. // // For `StringContext`, enter `s`, `f` and `raw` diff --git a/compiler/src/dotty/tools/dotc/transform/BeanProperties.scala b/compiler/src/dotty/tools/dotc/transform/BeanProperties.scala index baeb9cdbea98..91ec94590284 100644 --- a/compiler/src/dotty/tools/dotc/transform/BeanProperties.scala +++ b/compiler/src/dotty/tools/dotc/transform/BeanProperties.scala @@ -31,7 +31,7 @@ class BeanProperties(thisPhase: DenotTransformer): val meth = newSymbol( owner = ctx.owner, name = prefixedName(prefix, valDef.name), - flags = Method | Synthetic, + flags = Method | Synthetic | Invisible, info = MethodType(Nil, valDef.denot.info), coord = annot.tree.span ).enteredAfter(thisPhase).asTerm @@ -45,7 +45,7 @@ class BeanProperties(thisPhase: DenotTransformer): val meth = newSymbol( owner, name = prefixedName("set", valDef.name), - flags = Method | Synthetic, + flags = Method | Synthetic | Invisible, info = MethodType(valDef.name :: Nil, valDef.denot.info :: Nil, defn.UnitType), coord = annot.tree.span ).enteredAfter(thisPhase).asTerm From 4f2e6a43c772b016bdde3a23892e44759b2f5448 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Feb 2021 17:00:06 +0100 Subject: [PATCH 3/6] Don't create constructor proxies for invisible classes --- compiler/src/dotty/tools/dotc/core/NamerOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index c45e820c0e90..ce525fc5d827 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -68,7 +68,7 @@ object NamerOps: } /** If a class has one of these flags, it does not get a constructor companion */ - private val NoConstructorProxyNeededFlags = Abstract | Trait | Case | Synthetic | Module + private val NoConstructorProxyNeededFlags = Abstract | Trait | Case | Synthetic | Module | Invisible /** The flags of a constructor companion */ private val ConstructorCompanionFlags = Synthetic | ConstructorProxy From b4dfb6b0be11971de08915a903575692edcc8d90 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Feb 2021 11:27:51 +0100 Subject: [PATCH 4/6] Bump experimental instead of minor version --- tasty/src/dotty/tools/tasty/TastyFormat.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index ce3b642ea5e2..a86b066251fa 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -285,7 +285,7 @@ object TastyFormat { * compatibility, but remains backwards compatible, with all * preceeding `MinorVersion`. */ - final val MinorVersion: Int = 1 + final val MinorVersion: Int = 0 /**Natural Number. The `ExperimentalVersion` allows for * experimentation with changes to TASTy without committing @@ -301,7 +301,7 @@ object TastyFormat { * is able to read final TASTy documents if the file's * `MinorVersion` is strictly less than the current value. */ - final val ExperimentalVersion: Int = 1 + final val ExperimentalVersion: Int = 2 /**This method implements a binary relation (`<:<`) between two TASTy versions. * We label the lhs `file` and rhs `compiler`. From a7a97b41d7597eb95d190d590fa4b3ead8bcf6fb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Mar 2021 16:42:39 +0100 Subject: [PATCH 5/6] Address review comments --- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- tasty/src/dotty/tools/tasty/TastyFormat.scala | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 526573921b44..52b97fcba891 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -588,7 +588,7 @@ class TreeUnpickler(reader: TastyReader, val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased) ctx.owner match { case cls: ClassSymbol if !isScala2MacroDefinedInScala3 || cls == defn.StringContextClass => - // Enter all members of classes that are not Scala 2 macros or synthetic bean accessors. + // Enter all members of classes that are not Scala 2 macros. // // For `StringContext`, enter `s`, `f` and `raw` // These definitions will be entered when defined in Scala 2. It is fine to enter them diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index a86b066251fa..f0f5739e9c92 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -211,8 +211,8 @@ Standard-Section: "ASTs" TopLevelStat* PARAMsetter -- The setter part `x_=` of a var parameter `x` which itself is pickled as a PARAM PARAMalias -- Parameter is alias of a superclass parameter EXPORTED -- An export forwarder - OPEN - INVISIBLE -- an open class + OPEN -- an open class + INVISIBLE -- invisible during typechecking Annotation Variance = STABLE -- invariant @@ -473,9 +473,9 @@ object TastyFormat { final val PARAMalias = 41 final val TRANSPARENT = 42 final val INFIX = 43 - final val EMPTYCLAUSE = 44 - final val SPLITCLAUSE = 45 - final val INVISIBLE = 46 + final val INVISIBLE = 44 + final val EMPTYCLAUSE = 45 + final val SPLITCLAUSE = 46 // Cat. 2: tag Nat From 54495b60c1a2109e087cd35a0a3917b196c24255 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Mar 2021 16:42:57 +0100 Subject: [PATCH 6/6] Expose Invisible flag in Tasty reflect --- compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala | 1 + library/src/scala/quoted/Quotes.scala | 3 +++ 2 files changed, 4 insertions(+) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 746ea78e0ba9..60f4ee415897 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2664,6 +2664,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def Implicit: Flags = dotc.core.Flags.Implicit def Infix: Flags = dotc.core.Flags.Infix def Inline: Flags = dotc.core.Flags.Inline + def Invisible: Flags = dotc.core.Flags.Invisible def JavaDefined: Flags = dotc.core.Flags.JavaDefined def JavaStatic: Flags = dotc.core.Flags.JavaStatic def Lazy: Flags = dotc.core.Flags.Lazy diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 31c840e49578..08254e1b1113 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3884,6 +3884,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Is this symbol `inline` */ def Inline: Flags + /** Is this symbol invisible when typechecking? */ + def Invisible: Flags + /** Is this symbol defined in a Java class */ def JavaDefined: Flags