Skip to content

Inherited visibility has a misleading location #780

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

Closed
jrvidal opened this issue Mar 30, 2020 · 9 comments
Closed

Inherited visibility has a misleading location #780

jrvidal opened this issue Mar 30, 2020 · 9 comments

Comments

@jrvidal
Copy link

jrvidal commented Mar 30, 2020

Not sure if this is a bug or a feature, but it surprised me while trying to calculate the "real span" of some items (as in "the span of this item ignoring its attributes").

Test case

use syn::spanned::Spanned;

fn main() {
    let item: syn::ItemExternCrate = syn::parse_str(
      "#[foo]
      extern crate foo;
    ").unwrap();
    
    println!("attribute = {:?}", (item.attrs[0].span().start(), item.attrs[0].span().end()));
    println!("vis       = {:?}", (item.vis.span().start(), item.vis.span().end()));
    println!("`extern`  = {:?}", (item.extern_token.span().start(), item.extern_token.span().end()));

    println!("");

    let item = syn::parse_str::<syn::ItemExternCrate>(
      "#[foo]
      pub extern crate foo;
    ").unwrap();
    
    println!("attribute = {:?}", (item.attrs[0].span().start(), item.attrs[0].span().end()));
    println!("vis       = {:?}", (item.vis.span().start(), item.vis.span().end()));
    println!("`extern`  = {:?}", (item.extern_token.span().start(), item.extern_token.span().end()));
}

Output:

attribute = (LineColumn { line: 1, column: 0 }, LineColumn { line: 1, column: 6 })
vis       = (LineColumn { line: 1, column: 0 }, LineColumn { line: 1, column: 0 })
`extern`  = (LineColumn { line: 2, column: 6 }, LineColumn { line: 2, column: 12 })

attribute = (LineColumn { line: 1, column: 0 }, LineColumn { line: 1, column: 6 })
vis       = (LineColumn { line: 2, column: 6 }, LineColumn { line: 2, column: 9 })
`extern`  = (LineColumn { line: 2, column: 10 }, LineColumn { line: 2, column: 16 })

I would expect... maybe an empty span just before the extern_token?

@dtolnay
Copy link
Owner

dtolnay commented Mar 30, 2020

Rust associates spans only with tokens; every token has a span. If there isn't a token somewhere, there isn't a span that can represent the place where the token isn't.

@dtolnay dtolnay closed this as completed Mar 30, 2020
@dtolnay
Copy link
Owner

dtolnay commented Mar 30, 2020

I'll close this because I don't expect to diverge from the proc macro model on this.

For calculating the span without attributes, you should be able to use: ItemExternCrate { attrs: Vec::new(), ..item.clone() }.span()

@jrvidal
Copy link
Author

jrvidal commented Mar 30, 2020

For calculating the span without attributes, you should be able to use: ItemExternCrate { attrs: Vec::new(), ..item.clone() }.span()

Thanks 👍 I will try some workaround.

If there isn't a token somewhere, there isn't a span that can represent the place where the token isn't.

I don't expect to diverge from the proc macro model

Hm, OK, but by introducing an Inherited variant, we're already choosing to expose a fake, empty Span, aren't we? Would we diverge more from the proc macro model by choosing to move that fake span somewhere else?

@dtolnay
Copy link
Owner

dtolnay commented Mar 30, 2020

Inherited isn't a token, it is an empty enum variant representing absence of any tokens. There isn't a span in it. https://docs.rs/syn/1.0.17/syn/enum.Visibility.html

@jrvidal
Copy link
Author

jrvidal commented Mar 30, 2020

Sure, but even if there is no span for Inherited, you can still call to <Visibility as Spanned>::span().

Anyway, after looking a bit how it is implemented under the hood, I realize now what you're saying. There's no easy way to have a custom, fake Span if they ultimately come from the generated tokens of ToTokens. And I see now that this gotcha it's perfectly documented in Spanned::span() 🤦‍♀️ Sorry about the noise.

@dtolnay
Copy link
Owner

dtolnay commented Mar 30, 2020

More generally, whether or not ToTokens is involved in the current implementation, it's that there isn't any token to identify the place where the token isn't.

pub enum Visibility {
    ...
    Inherited,
}

impl Visibility {
    pub fn span(&self) -> Span {
        match self {
            ...
            Visibility::Inherited => ???
        }
    }
}

@jrvidal
Copy link
Author

jrvidal commented Mar 30, 2020

Out of curiosity, what's the reason to have an empty variant instead of Option<Visibility>?

@dtolnay
Copy link
Owner

dtolnay commented Mar 30, 2020

Because it's always what you want. I don't know of any syntax that would want to parse a visibility but not allow the empty visibility.

@ratijas
Copy link

ratijas commented Apr 22, 2020

By the way, compiler explicitly claims to parse :vis macro meta-variable as "Possibly empty". Although, that has some restrictions at the moment (see rust-lang/rust#71422).

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

3 participants