-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4058: reject sealed and lazy for class parameters in parser #4070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
tests/neg/i4058.scala
Outdated
@@ -4,6 +4,6 @@ class C(abstract val a: Int) // error | |||
class D { | |||
def f(sealed a: Int) = 0 // error | |||
def g(lazy a: Int) = 0 // error | |||
def g(ov erride a: Int) = 0 //error | |||
def g(override a: Int) = 0 // error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hadn't found testCompilation i4058
yet, my bad for not reading http://dotty.epfl.ch/docs/contributing/testing.html 😅
The modifier parser can take a set of allowed modifier as argument. But I believe in the case of |
Did that, then improved reporting: when |
@@ -1839,7 +1849,10 @@ object Parsers { | |||
val start = in.offset | |||
var mods = annotsAsMods() | |||
if (owner.isTypeName) { | |||
mods = modifiers(start = mods) | ParamAccessor | |||
// Adding ParamAccessor would crash | |||
mods = modifiers(start = mods, allowed = modifierTokens &~ BitSet(SEALED)) | ParamAccessor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not modifierTokens - SEALED
?
@@ -2451,7 +2464,7 @@ object Parsers { | |||
|
|||
/** BlockStatSeq ::= { BlockStat semi } [ResultExpr] | |||
* BlockStat ::= Import | |||
* | Annotations [implicit] [lazy] Def | |||
* | Annotations [implicit] [ghost] [lazy] Def |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ghost/erased
@@ -1714,7 +1722,7 @@ object Parsers { | |||
* | override | |||
* LocalModifier ::= abstract | final | sealed | implicit | lazy | |||
*/ | |||
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { | |||
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers(), parsedSeparately: BitSet = BitSet.empty): Modifiers = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not convinced by this change of API. If the caller wants to parse some modifier separately, then he can exclude them from the allowed modifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To get the nicer error I need to distinguish "not allowed" (which gives an error) from "parsed separately" (which doesn't). I had to use parsedSeparately
only once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the API is too hard for the error, I can revert/separate these refactorings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a better API would something along the line of:
/** ...
* @param errorOn emit a syntax error when a parsed modifier is in errorOn
*/
def modifiers(allowed: BitSet, start: Modifiers, errorOn: BitSet) = {
...
if (allowed.contains(in.token) { ...; loop() }
else if (errorOn.contains(in.token) { syntaxError; loop() }
else ...
}
You can keep parsing modifiers after encountering an invalid modifier.
But since it is used only once, I think we should no complicate this API.
I noticed `sealed` while reviewing `modifiers`.
This might be overkill, since these modifiers aren't expected here.
Missing: tests. Had to add to `modifiers` a new `parsedSeparately` parameter, and since its interface is tricky I wrote some documentation.
Rebased and addressed most comments. |
@@ -2468,7 +2481,7 @@ object Parsers { | |||
else if (isDefIntro(localModifierTokens)) | |||
if (in.token == IMPLICIT || in.token == ERASED) { | |||
val start = in.offset | |||
var imods = modifiers(funArgMods) | |||
var imods = modifiers(funArgMods, parsedSeparately = BitSet(LAZY)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this, the current error message is good:
72 | lazy def bar = 1
| ^
| modifier `lazy` is not allowed for this definition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s not the error message I’m fixing. Will separate the PR and add examples.
@@ -1714,7 +1722,7 @@ object Parsers { | |||
* | override | |||
* LocalModifier ::= abstract | final | sealed | implicit | lazy | |||
*/ | |||
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { | |||
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers(), parsedSeparately: BitSet = BitSet.empty): Modifiers = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a better API would something along the line of:
/** ...
* @param errorOn emit a syntax error when a parsed modifier is in errorOn
*/
def modifiers(allowed: BitSet, start: Modifiers, errorOn: BitSet) = {
...
if (allowed.contains(in.token) { ...; loop() }
else if (errorOn.contains(in.token) { syntaxError; loop() }
else ...
}
You can keep parsing modifiers after encountering an invalid modifier.
But since it is used only once, I think we should no complicate this API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flag checking is generally done in Namer using checkWellFormed
. I think this is where these checks should go as well.
SEALED must be rejected here, otherwise adding ParamAccessor will crash right away. |
I believe we need a more systematic fix here. The root of the problem is that there are term flags and type flags and we are adding a term flag to a type flag or vice versa, which is not supported. We should use in Parser a general flag combining method which detects and reports this problem. But don't change the parsed syntax! The syntax is given by |
It turned out that the parser did already what I suggested. There was just one instance where the correct combiner method was not called. #4126 fixes this. |
Fix #4058.
Thanks to @allanrenucci for guidance. Rejecting
lazy
there follows Scalac, whilesealed
must be rejected there because of our consistency checks.Intentionally mixing in small fixes/tests nearby.
Wondering about the strategy; I guess using a general
modifiers
parser and then rejecting most modifiers does make sense to offer better error messages, compared to only accepting the actually sensible modifiers.