Skip to content
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

Fix: Disallow setters with multiple arguments #6324

Merged

Conversation

maxfierke
Copy link
Contributor

Fixes #5856 by throwing a syntax error when defining a setter method that accepts more than one argument, seeing as there is no way to call it.

@@ -3344,6 +3347,10 @@ module Crystal
index += 1
end

if is_setter && args.size > 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't is_setter be replaced by name.ends_with?('=')?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah! good catch

@asterite
Copy link
Member

asterite commented Jul 4, 2018

Also check named args, no splat, etc.

@maxfierke
Copy link
Contributor Author

@asterite added some checks for named args, splat, kwargs, and then also block args, since there's a similar issue calling those as well

@@ -3332,6 +3333,7 @@ module Crystal
if block_arg = extras.block_arg
compute_block_arg_yields block_arg
check :")"
found_block = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!block_arg.nil? == found_block

So the found_block var can be removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, but I also see found_splat, etc., so it's fine to keep this variable 👍

@@ -174,6 +174,13 @@ module Crystal
assert_syntax_error "def foo!=; end", "unexpected token: !="
assert_syntax_error "def foo?=(x); end", "unexpected token: ?"

# 5856
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# #5856

@@ -174,6 +174,13 @@ module Crystal
assert_syntax_error "def foo!=; end", "unexpected token: !="
assert_syntax_error "def foo?=(x); end", "unexpected token: ?"

# 5856
assert_syntax_error "def foo=(a,b); end", "setter method 'foo=' cannot receive more than one argument"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/receive/have/. "receive" is more associated to the call site. Same for the others. Just my opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems reasonable. have is a better hint at something static too.

@@ -3344,6 +3346,14 @@ module Crystal
index += 1
end

if name.ends_with?('=') && !name.ends_with?("[]=")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second test can be name == "[]=". There can't be anything before []= in a method name.

However, is there a particular reason why []= can be called like any method but all other setters can't?
If not, the same rules should apply (with args.size != 2).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With []= you can do var[1, 2] = 3 to implement a matrix type for example

Copy link
Member

@straight-shoota straight-shoota Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that's a great use case. You can even do var[1, foo: 2] = 3 👀

But it still doesn't explain foo.[]=(foo) do |baz| end 😃

Copy link
Contributor Author

@maxfierke maxfierke Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doh! I'll switch that to an equality check on name. @bew's totally right on with the []= with multi-arg case. There's at least one place in stdlib where []= is given splat args and kwargs too.

Good catch on the block case for []=. I'll address that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# If we have `f.x=(exp1).a.b.c`, consider it the same as `f.x = (exp1).a.b.c`
# and not as `(f.x = exp1).a.b.c` because a difference in space
# should not make a difference in semantic (#4399)
# The only exception is doing a splat, in which case this can only
# be expanded arguments for the call.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And because (exp1, exp2) is not a valid syntax, f.x=(exp1, exp2) is not a valid syntax either, so there's no way to pass multiple arguments there. It's the same as in Ruby (I copied it from Ruby, actually).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the exception for splat mentioned there maybe should be removed (it doesn't work in Ruby, so...)

@straight-shoota
Copy link
Member

yield should also be a syntax error in a setter method. It's not part of the method signature, so it must be implemented differently, and doesn't need to be in this PR.

@straight-shoota
Copy link
Member

@maxfierke []= can currently receive a block argument. This should only be disallowed in the method signature if it doesn't work at call site. We could change that, but it isn't yet. And maybe it shouldn't.

@straight-shoota
Copy link
Member

To clarify: Currently, []= can be called with a block (var.[]= {}). I just think we shouldn't disallow the declaration without disallowing the call. Either we allow both or disallow both. I'm not sure about it... and it probably doesn't make a any difference whatsoever.

@RX14 RX14 added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler labels Jul 16, 2018
Copy link
Member

@sdogruyol sdogruyol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @maxfierke 👍

@RX14 RX14 requested a review from bcardiff July 30, 2018 16:10
Copy link
Member

@bcardiff bcardiff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the special treatment of []= regarding blocks.

Where are setters with blocks used in the stdlib? Maybe it is better to disallow blocks in setters.

So far the only difference of []= with respect other setters is that []= allows multiple arguments because of multiple indices. m[i, j] = v

@maxfierke
Copy link
Contributor Author

@bcardiff Are you referring to the changes in src/object.cr?

blocks are disallowed for all setters with this PR. The special case here for []= is to avoid generating block-supporting method forwards for []=.

@bcardiff
Copy link
Member

bcardiff commented Aug 8, 2018

Oh, I missed that it was inside a method.id.ends_with?('='). So that's fine.

But I am still missing why there is an if name == "[]=" in the changeset for parser.cr. Yet the specs clearly says that foo=(&block) is disallowed 🤔

@maxfierke
Copy link
Contributor Author

@bcardiff ahhh! I assume you're referring to here. That is to disallow calls to []= with a block at the call site in response to this feedback: #6324 (comment). This way, users get a compiler error instead of a silent failure if for some reason they're calling []= with a block somewhere and expecting it to work (e.g. doing so caught the invalid generation via macro of the aforementioned block-supporting forward for []= in src/object.cr)

@bcardiff
Copy link
Member

bcardiff commented Aug 8, 2018

Ahhh! The parsing snippet is because []= is parsed as [, args, ]. =, etc. So the stressed path is not the same as method=

@bcardiff bcardiff merged commit 0ff17f1 into crystal-lang:master Aug 8, 2018
@bcardiff bcardiff added this to the 0.26.0 milestone Aug 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants