-
Notifications
You must be signed in to change notification settings - Fork 2k
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
[CS2] Throw an error for ambiguous get
or set
keywords or function calls
#4484
Conversation
…ter/setter keywords, to warn the user to use parentheses if they intend a function call (or to inform them that `get` or `set` cannot be used as a keyword)
src/lexer.coffee
Outdated
@@ -170,6 +170,8 @@ exports.Lexer = class Lexer | |||
isForFrom(prev) | |||
tag = 'FORFROM' | |||
@seenFor = no | |||
else if tag is 'PROPERTY' and prev and prev.spaced and prev[0] in CALLABLE and /^[g|s]et$/.test(prev[1]) |
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.
The [g|s]
should simply be [gs]
.
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.
Damn. You win.
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.
For the record, that's not just shorter but also more correct. [g|s]et
would also match |et
.
63c3ccf
to
725fe8e
Compare
Would it make sense to catch |
|
The PR will also catch |
I would have said LGTM unless I had read @vendethiel's comments. Now I'm not sure. |
…ike a function or method with an interpolated/dynamic name
Okay, I’ve caught [stdin]:1:12: error: 'get' cannot be used as a keyword, or as a function call without parentheses
class then get @a = 5
^^^ Catching interpolated property names took some doing, but I managed to figure it out: [stdin]:1:1: error: 'get' cannot be used as a keyword, or as a function call without parentheses
get "a": 5
^^^
[stdin]:1:1: error: 'get' cannot be used as a keyword, or as a function call without parentheses
get "#{a}": 5
^^^ And So my first attempt at this was to try to implement it within @vendethiel thanks for being so thorough! |
@vendethiel or @lydell, do you think you’ll have any further notes on this? I’m eager to start wrapping up the PRs for 2.0.0-beta1 (of which this is one). |
src/lexer.coffee
Outdated
# Get the previous token in the token stream. | ||
prev: -> | ||
[..., token] = @tokens | ||
token if token? |
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 those two lines can be replaced with @tokens[@tokens.length - 1]
- if there's no token, it'll be undefined
I tried to think of a way to detect string properties, but outside of creating a special "get/set spaced" token type that the parser would catch, I don't think it's feasible. |
…ent is a string without a colon (so a plain string, not a property accessor)
@vendethiel How about this? 2d1addf |
I think we've reached the point of diminishing return, as we now error out on |
Well I should revert my last commit at least. The question is, so I also revert the support for throwing an error on |
I 👍 the latter – not worth burdening. |
@lydell do you agree? |
…st argument is a string without a colon (so a plain string, not a property accessor)" This reverts commit 2d1addf.
# Conflicts: # lib/coffeescript/lexer.js
Yes, I agree. |
…with dynamic property names (introduces a way to circumvent our check for trying to avoid the `get` or `set` keywords, but not worth the complications for this tiny edge case)
@lydell @vendethiel okay, I took out the check for “calls” to objects with dynamically-named properties. Care to do a final check before we merge this in? |
Hey folks, I need to pitch in here: I've been running into the need for getters and setters A LOT lately. This is becoming more and more of a pain from using Coffeescript and is making me slowly reconsider that, and I'm speaking as a defender and long time user for years. The problem is that the workaround is pretty long winded, and hard to organize neatly. I've read the documentation about it probably a dozen times now, I'm still not able to learn it by heart. I haven't used the workaround once. I'm not even sure someone will read this, but I needed to document this somewhere. It seems like more and more, frameworks and libraries are offering getters and setters as the standard way to do things (I'm working with Vue at the moment). Moreover, they're a quite useful tool for a lot of cases. CS, with the experience it provides, pretty much discourages the practice. Is there really no other syntax that would work? Any workaround would be better:
anything, but please don't make me type Anyway, I don't want to rain on anyone's decisions, it's just important to me to give some feedback about the impact of this on my development experience. The reason I use Coffeescript is because it makes my experience better, and this is starting to make a dent in that, unfortunately. |
I also use Vue with CoffeeScript, and I haven't run into this issue. Perhaps you can give a realistic example? I do find myself creating computed properties with getters and setters, but the syntax looks like this: computed: {
...mapGetters 'user', ['profile']
token:
get: -> @profile?.token or ''
set: (val) -> @newToken = val
} The issue with You're right in that getters and setters seem to be getting more popular as frameworks use them to enable reactivity. I would be open to a shorthand syntax if it doesn't create a breaking change. If you can think of one (play around with https://coffeescript.org/#try until your proposed syntax throws an error on the right) then please feel free to propose it. |
@GeoffreyBooth Thanks for considering this. About your example, unless I'm mistaken, this doesn't really create computed properties. So maybe Vue just understands that magically? If so, neat. I forget my other use cases because I have terrible memory, but this time I was trying to avoid making I completely agree that Throwing a few ideas: obj = {
hiddenToken: 1
token:
.get: -> @hiddenToken
.set: (val) -> @hiddenToken = val
}
"""
[stdin]:4:5: error: unexpected .
.get: -> @hiddenToken
^
"""
obj = {
hiddenToken: 1
token:
:get: -> @hiddenToken
:set: (val) -> @hiddenToken = val
}
obj = {
hiddenToken: 1
token:
@get: -> @hiddenToken
@set: (val) -> @hiddenToken = val
}
# Same error
# Other available prefixing symbols:
# % ? & * =
obj = {
hiddenToken: 1
token.get: -> @hiddenToken
token.set: (val) -> @hiddenToken = val
}
"""
[stdin]:3:12: error: unexpected :
token.get: -> @hiddenToken
^
"""
obj = {
hiddenToken: 1
`get token()`: -> @hiddenToken
`set token()`: (val) -> @hiddenToken = val
}
# or something similarly literal
"""
[stdin]:3:3: error: unexpected get token()
`get token()`: -> @hiddenToken
^^^^^^^^^^^^^
"""
# If I try to get creative, maybe some symbols make some deeper sense?
obj = {
hiddenToken: 1
*token: -> @hiddenToken
&token: (val) -> @hiddenToken = val
}
"""
[stdin]:3:3: error: unexpected *
*token: -> @hiddenToken
^
"""
# Borrowing from C/Yaml's dereferencing operator, and Ruby setter methods:
obj = {
hiddenToken: 1
token*: -> @hiddenToken
token=: (val) -> @hiddenToken = val
} I'm happy to pitch in some dev efforts if we find something that has some kind of consensus. |
I think this syntax is the one most likely to find consensus: obj =
hiddenToken: 1
`get token`: -> @hiddenToken
`set token`: (val) -> @hiddenToken = val Mainly because it's not actually about getters and setters at all. It's just adding another place where the One issue though is that at least in my tests, it appears that getters and setters require the method syntax? So like this: obj = {
hiddenToken: 1,
get token() { return this.hiddenToken; },
set token(val) { this.hiddenToken = val; }
}; Whereas The good news is, I think that should be fine, and an improvement. If you look at classes, we already output the method syntax there; see https://coffeescript.org/#try:class%20A%0A%20%20a%3A%20-%3E%20123. There's been an open issue for a while to bring that syntax over to objects: #5075. So step one would be to change the output of objects to use method syntax, e.g.: # Input:
obj =
fn: -> 123 // Current output:
obj = {
fn: function() {
return 123;
}
};
// Desired output:
obj = {
fn() {
return 123;
}
}; That should be its own PR, and would resolve #5075. Then step two would be to support a JavaScript passthrough as the method name: obj =
`fn`: -> 123 Once that compiles (and produces the same output as if you had |
That would be a huge step forward actually. So we could have: obj =
val: 1
`get value`: -> @val
`set value`: (val) -> @val = val compiles to obj = {
val: 1,
function get value() {
return this.val;
},
function set value(val) {
return this.val = val;
}
}; This doesn't seem too much of a stretch from the current behavior. So if I understand correctly, after #5075, such a PR would be welcome? |
This PR implements our consensus for how to handle getters and setters, or more precisely the
get
andset
keywords, in CS2:The
get
andset
shorthand syntax is too infrequently used, and a discouraged practice, for CoffeeScript to support directly. Getters and setters can be created via theObject.defineProperty
method, so they technically already are supported in CoffeeScript; supporting the shorthand syntax as well just makes them more convenient to use, but Douglas Crockford argues that we should rarely if ever be using them.So the task for CS2 is to have the compiler throw an error when it appears that a
get
orset
shorthand syntax keyword is being used. Things like the following:And
set
for all of the same. These should throw compiler errors, without prohibiting people from using variables or functions namedget
orset
elsewhere in the code. Basically when a call to a function namedget
orset
is given an argument that is an object, and that function is called without parentheses, we throw an error. All someone has to do to use their function namedget
orset
is to call it with parentheses, e.g.get({b: ->})
If we someday change our minds and decide to support
get
andset
shorthand keywords, the work has already been done in this PR to recognize the tokens. They would need to be added to the grammar as keywords and output accordingly. Thanks to this PR, it wouldn’t be a breaking change.@connec @mrmowgli @carlsmith