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

Don't leak variable name when assigning to ivar/cvar in method signature #6007

Closed
wants to merge 4 commits into from

Conversation

asterite
Copy link
Member

Fixes #5997
Fixes #4332

The idea is that this:

def foo(@var)
end

expands to this:

def foo(var __arg0)
  @var = __arg0 # the name __arg0 could be another one, you don't know it
end

instead of:

def foo(var)
  @var = var
end

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.

name = "__arg#{@temp_arg_count}"
@temp_arg_count += 1
name
end
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

@bew
Copy link
Contributor

bew commented Apr 25, 2018

I don't like the sound of this.. How do you resolve this then: #3413

@@ -1047,12 +1047,14 @@ module Crystal

def initialize(program, namespace, name, @superclass, add_subclass = true)
super(program, namespace, name)
superclass = @superclass
Copy link
Member

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

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?

Copy link
Member

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.

Copy link
Member Author

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

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.

Copy link
Member

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?.

Copy link
Member Author

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.

Copy link
Contributor

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

@asterite
Copy link
Member Author

@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 = in those places, but you can anyway blow up at runtime if you call a method that accesses an uninitiaized instance var (but that's yet another issue to fix)

@bew
Copy link
Contributor

bew commented Apr 25, 2018

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 @b = @a to @b = __var0?

@straight-shoota
Copy link
Member

@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 a refers to the getter.

@asterite
Copy link
Member Author

That won't work anymore due to this PR.

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 @a thingy in method arguments. It's incomplete. It's a nice idea, but (as many other features in the language) it's incomplete and it will probably never be implemented well. So, instead, I'd like to remove features that can't be properly implemented.

@asterite
Copy link
Member Author

@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.)

@asterite
Copy link
Member Author

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.

@asterite asterite closed this Apr 25, 2018
@straight-shoota
Copy link
Member

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

@bew
Copy link
Contributor

bew commented Apr 25, 2018

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?

@asterite
Copy link
Member Author

@straight-shoota Right, and the problem is that a_method is invoked before @a ever being assigned, hence the segfault/bad-memory.

See how that feature is not intuitive at all?

@asterite
Copy link
Member Author

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...

@asterite
Copy link
Member Author

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.

@RX14
Copy link
Contributor

RX14 commented Apr 25, 2018

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.

@straight-shoota
Copy link
Member

@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.

@asterite asterite reopened this Apr 27, 2018
@asterite
Copy link
Member Author

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 var().

What do you think?

@asterite
Copy link
Member Author

I don't understand why it only fails in Travis :-(

@asterite asterite force-pushed the feature/no-implicit-instance-var branch from 49b239d to 2b3c696 Compare April 28, 2018 12:56
@sdogruyol sdogruyol changed the title Don't leak variable name when assining to ivar/cvar in method signature Don't leak variable name when assigning to ivar/cvar in method signature May 7, 2018
@sdogruyol
Copy link
Member

This deserves more ❤️

@asterite
Copy link
Member Author

I think this should be closed. The current behaviour is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants