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

self variable in macros is broken (regression) #15682

Closed
netvl opened this issue Jul 15, 2014 · 11 comments
Closed

self variable in macros is broken (regression) #15682

netvl opened this issue Jul 15, 2014 · 11 comments

Comments

@netvl
Copy link
Contributor

netvl commented Jul 15, 2014

This code does not compile (playpen), though it should:

#![feature(macro_rules)]

macro_rules! self_call(
    ($name:ident) => (self.$name())
)

struct X;

impl X {
    fn x(&self) {
        self_call!(y)
    }

    fn y(&self) {
        println!("Called y()");
    }
}

fn main() {
    X.x();
}

This is the error:

<anon>:4:23: 4:27 error: `self` is not available in a static method. Maybe a `self` argument is missing?
<anon>:4     ($name:ident) => (self.$name())
                               ^~~~
<anon>:3:1: 5:2 note: in expansion of self_call!
<anon>:11:9: 12:6 note: expansion site
error: aborting due to previous error

This worked previously (it certainly did on June, 8th - this is the last time I compiled my code which used such macro).

@huonw
Copy link
Member

huonw commented Jul 15, 2014

This is macro hygiene, and is by design: you can't magically capture a variable that the macro can't see when it is defined (very much similar to a normal closure, which can only capture variables that are in scope when the closure is defined).

You either need to define the macro inside the method (so that the self name is in scope), or pass in the identifiers from the external scope, i.e.

macro_rules! self_call(
    ($self_:ident, $name:ident) => ($self_.$name())
)

// ...

    fn x(&self) {
        self_call!(self, y)
    }

The reason it worked before is our macro hygiene implementation was not complete (@jbclements has been making good progress on closing the remaining major holes, as the compilation failure of the code in this bug indicates).

(Closing as not-a-bug.)

@huonw huonw closed this as completed Jul 15, 2014
@jbclements
Copy link
Contributor

One follow-up; it's now possible to have macros that expand into methods, so you could also just write

macro_rules! self_calling_method(
  ($name:ident) => (fn x(&self){self.$name()})
)

//...
  self_calling_method(y)

... if that's what you wanted.

@netvl
Copy link
Contributor Author

netvl commented Jul 15, 2014

@huonw, shame on me, it's so obvious :( I guess it is self highlighting which affected me, and also the fact that it did work previously.

@jbclements, no, unfortunately, this is not what I needed, but it is nice that it is possible. However, as far as I remember, this can be emulated by expanding into multiple impls, isn't it? Something like

macro_rules! self_calling_method(
    ($m:ident, $name:ident) => (impl SomeType {
        fn $m(&self) { self.$name() }
    })
)

self_calling_method!(a, b)
self_calling_method!(d, e)

@jbclements
Copy link
Contributor

@netvl yes, definitely.

@huonw
Copy link
Member

huonw commented Jul 15, 2014

(Unless the method is a trait method, in which case you need to define all such methods in the single impl Trait for ... { ... }.)

@Ryman
Copy link
Contributor

Ryman commented Mar 8, 2015

@huonw @jbclements Do you know if self is planned to not be a keyword in the far future? Otherwise we could probably allow self in macros since it can only really ever reference one thing so there's no hygiene risk?

This would allow us to have some pretty big ergonomic wins through macros if we reconsider this.
e.g. consider the current requirement:

struct Foo(u8);

macro_rules! bit(
    ($_self:expr, $i:expr) => { $_self.0 & (1 << $i) != 0 };
);

impl Foo {
    fn is_bar(self) -> bool { bit!(self, 1) }
}

fn main() { 
    println!("{}", Foo(0x00).is_bar()); 
    println!("{}", Foo(0x02).is_bar());
}

vs the potential

macro_rules! bit(
    ($i:expr) => { self.0 & (1 << $i) != 0 };
);

impl Foo {
    fn is_bar(self) -> bool { bit!(1) }
}

Note: we can't even use $self in macros due to the keyword

@jbclements
Copy link
Contributor

I bet there's an interesting way in which removing this hygiene could cause a nasty future bug. I can't quite put my finger on it, though.

@huonw
Copy link
Member

huonw commented Mar 11, 2015

We do allow nested items, e.g.

impl Foo {
    fn bar(&self) {
        struct Inner;
        impl Inner {
            fn baz(&self) {}
        }
    }
}

Removing self hygiene may allow some "invalid" code to compile, e.g. if one defines a macro like

macro_rules! make_method {
     ($e: expr) => { fn method(&self) { $e } }
}

and calls it like

fn bar(&self) {
     struct Inner;
     impl Inner {
         make_method!(self.qux()) // meant to be the outer `self`
     }
}

However, that seems like like a tiny edge case that will rarely crop up.

(That said, as a simpler example, I guess being able to write impl Foo { make_method!(self.qux()) } is strange.)

@Ryman
Copy link
Contributor

Ryman commented Mar 11, 2015

@jbclements It's likely! My understanding of the system is shallow :)

@huonw The nested case is an issue I hadn't considered, I guess a better suggestion would be to introduce $self to be allowed (not in conflict with the keyword), and to refer to outer self?:

macro_rules! call_foo {
    ($e:expr) => { $self.foo($e) }
}

Where: Any macro that uses $self, taints the entire macro to require that all patterns will have an implicit $self: prepended. Macros can be used in the form x.y.z.call_foo!(some_expr) would expand to call_foo!(x.y.z: some_expr) which would allow a contextual self, and call_foo!(..) with no dot would expand to call_foo!(self: ..).

Would this suffice for hygiene since self = $self for inputs?.
In your nested example, this should then error out as parent self shouldn't be accessible. It should also error out if the inner body referenced $self as $self != (inner)self?
These macros would fail to expand within a function, but resolve would already error for that, similar to the current error message with its weird static method error.

@tinco
Copy link
Contributor

tinco commented May 1, 2016

Is it safe to say this issue has been left aside because the solution @Ryman suggested is very hard to implement?

I'd like to make a case for the initial 'just allow self' implementation. The implementation is likely one or two small conditionals in the linter, and it will enable a whole class of macros that are now blocked pending this bug.

Subsequently we can immediately file a bug that there is a case where nested implementations cause a problem. This more rare bug can then have a more focused discussion on how to fix it. Even if then it's decided that we should make it $self, it won't be a big problem for macro library authors to add the '$' to their macros when this more complex feature is implemented.

@havelund
Copy link

havelund commented Apr 8, 2019

The current solution is definitely painful. self should not act as an identifier here.

bors added a commit to rust-lang-ci/rust that referenced this issue Nov 13, 2023
Recover better on missing parameter in param list

We should do the same for argument lists, but that is more tricky to fix.
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

No branches or pull requests

6 participants