-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Don't leak variable name when assigning to ivar/cvar in method signature #6007
Conversation
name = "__arg#{@temp_arg_count}" | ||
@temp_arg_count += 1 | ||
name | ||
end |
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.
Can/Should this check if the name already exists in the current scope and discard it? Say I do
def foo(__arg0, @foo)
end
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 convention is that variables that start with __
are reserved for the compiler, so they shouldn't clash with user variables. If a user choose to name their variables __argN
, they are warned that that's problematic (at least in my mind :-P).
I don't want to make things more complex than they should, at this point.
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.
Mmh, not asking to do this here, but do you think it would actually be possible to make trying to use these a parser error then? I don't think it's too uncommon for somebody to try to use such variable names, especially when dealing with macros.
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'm not sure it can be done. Sometimes methods are passed to macros, and these are to_s
'd, and then reparsed again... and that would error when it shouldn't.
Maybe it's something to consider in the future.
Note that right now the __arg
or __temp
hack is used all over the place in the compiler (like, when you do if a() || b()
, this isn't something new. And so far it didn't cause any problem, so I guess it's fine.
I don't like the sound of this.. How do you resolve this then: #3413 |
src/compiler/crystal/types.cr
Outdated
@@ -1047,12 +1047,14 @@ module Crystal | |||
|
|||
def initialize(program, namespace, name, @superclass, add_subclass = true) | |||
super(program, namespace, name) | |||
superclass = @superclass |
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.
It might be a style issue, but I think it makes more sense the other way around: @superclass = superclass
(and superclass
as arg). That's how it worked implicitly before and prevents surprises caused by mismatching types between argument and ivar.
|
||
def initialize(@range : Range(B, E), @current = range.begin, @reached_end = false) | ||
def initialize(@range : Range(B, E)) |
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.
Here you're also changing the ctor signature, we can no more setup current & reached_end. Is that intentional?
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.
It's a private class. And the removed arguments don't seem to be used anywhere. Wouldn't make much sense anyway.
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 constructor was used internally, so no problem. Same in other places.
@@ -50,7 +50,8 @@ class Socket | |||
@addr6 : LibC::In6Addr? | |||
@addr4 : LibC::InAddr? | |||
|
|||
def initialize(@address : String, @port : Int32) | |||
def initialize(address : String, @port : Int32) | |||
@address = address |
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 this change?
Ditto a few times below, where inline ivar init is ok.
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.
address
is used in the next line and it should be type String
. @address
would be String?
.
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.
Because @address
is String?
and the methods/conditions bellow won't work. But address
is String
and that works.
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.
Oh wow, that's weird to read ^^ but makes sense, ty
@bew I don't know the fix to #3413 but I don't know how much it has to do with this PR. The fix is probably disallowing instance variables after |
I see it related by the suggested solution #3413 (comment) class A
getter :a, :b
def initialize(@a : Int32, @b : Int32 = a)
end
end
a = A.new a: 1
puts a.b # => 1 That won't work anymore due to this PR. To me this solution was beautifully clean, but I can understand why having an implicit local var can be unexpected.. Maybe that other issue could be solved (in a later PR) by changing |
@bew True that workaround won't work but I think that's a good thing. Shadowing ivar arguments as local vars can be unexpected. In this example it would seem that |
Right, you'll have to do: class A
getter :a, :b
def initialize(a : Int32, b : Int32 = a)
@a = a
@b = b
end
end Aaaaand... that's why I'd like to remove the |
@straight-shoota And in fact it will now refer to the getter, and give you 0 (uninitialized memory). That's a separate issue, though... but yeah, with this PR it's likely than a lot of code right now will compile just fine and behave in a weird way (segfaults, wrong memory, etc.) |
Meh, I don't know why I'm even trying... my conclusion, again, as usual, is that nothing can be changed/fixed at the moment because there are so many broken/missing things that changing one thing will break another thing. This whole thing needs to be redone (and first rethought) from scratch. |
Are you sure about those memory issues? I assume that'd only be a problem with constructors, right? And even there... what can break that's currently working and won't raise a compiler error with this PR? There shouldn't be an issue with uninitialized memory in the example- With the PR it's essentially going to be equal to this: class A
def a_method
@a
end
def initialize(@a : Int32, @b : Int32 = a_method)
end
end |
To me the old solution was beautifully clean, but I can understand why having an implicit local var can be unexpected.. What's wrong here? |
@straight-shoota Right, and the problem is that See how that feature is not intuitive at all? |
Essentially, it gets rewritten as: class A
def a_method
@a
end
def initialize(a : Int32)
initialize(a, a_method) # oops!
end
def initialize(a : Int32, b : Int32)
@a = a
@b = b
end
end But maybe someone can find a better way to implement that... |
Maybe we should always fully expand the body with named arguments... a lot of code duplication, and it will slow down compilation, but it's probably for the best. |
I like this PR, it shouldn't be closed. Yes, working on the compiler is frustrating but that's not a reason not to do it. It seems we're just missing a few rules about the scopes that default arguments can access. |
@asterite My code works fine right now. It gives a nice compiler error. I don't see a reason why this PR would change that. If there was really an issue, a simple fix would just be to disallow instance method calls as default arguments in constructors. |
Reopened. Do we really want to go forward with this change, or just documenting that: def foo(@var)
end expands to: def foo(var)
@var = var
end is good? I don't mind the second option. It's how the language has been working so far. And if you have a local method with that same name, just use What do you think? |
I don't understand why it only fails in Travis :-( |
49b239d
to
2b3c696
Compare
This deserves more ❤️ |
I think this should be closed. The current behaviour is fine. |
Fixes #5997
Fixes #4332
The idea is that this:
expands to this:
instead of:
so you don't have access to that "implicit" local variable anymore.
I think this is good. It's a breaking change, but it's more intuitive. If you want it assigned to a local variable, explicitly assign it to a local variable.