Skip to content

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

Closed
wants to merge 6 commits into from

Conversation

Blaisorblade
Copy link
Contributor

@Blaisorblade Blaisorblade commented Mar 4, 2018

Fix #4058.
Thanks to @allanrenucci for guidance. Rejecting lazy there follows Scalac, while sealed 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.

@@ -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
Copy link
Contributor Author

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 😅

@allanrenucci
Copy link
Contributor

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

The modifier parser can take a set of allowed modifier as argument. But I believe in the case of lazy it is worth keeping the custom error message. I would not special case sealed and simply exclude it from the set of the allowed modifiers for parameters.

@Blaisorblade
Copy link
Contributor Author

I would not special case sealed and simply exclude it from the set of the allowed modifiers for parameters.

Did that, then improved reporting: when modifiers runs into modifiers not allowed it says so, instead of "expected ... found 'sealed'". Code got trickier, so I added docs.

@@ -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
Copy link
Contributor

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
Copy link
Contributor

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 = {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.
@Blaisorblade
Copy link
Contributor Author

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))
Copy link
Contributor

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

Copy link
Contributor Author

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 = {
Copy link
Contributor

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.

Copy link
Contributor

@odersky odersky left a 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.

@Blaisorblade
Copy link
Contributor Author

SEALED must be rejected here, otherwise adding ParamAccessor will crash right away.
If you still want to move other checks later, I can try.

@odersky
Copy link
Contributor

odersky commented Mar 15, 2018

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 syntax.md and should be parsed as such.

@odersky odersky assigned Blaisorblade and unassigned odersky Mar 15, 2018
@odersky
Copy link
Contributor

odersky commented Mar 16, 2018

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.

@odersky odersky closed this Mar 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lazy in constructor parameter causes Nil$.head
3 participants