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

How to determine hygienic context for the "crate root" in absolute-by-default paths #50376

Closed
petrochenkov opened this issue May 1, 2018 · 5 comments
Labels
A-macros-2.0 Area: Declarative macros 2.0 (#39412) C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@petrochenkov
Copy link
Contributor

Procedural macros generate tokens, but sometimes the information is kept not in tokens, but rather in the lack of them (compare with #50130).

In particular, paths in use are resolved as absolute-by-default.
Effectively, they have one extra segment added at the start during name resolution

use a::b::c;

=>

use {{root}}::a::b::c;
// Or using the syntax from https://github.com/rust-lang/rust/issues/44660
use crate::a::b::c;

The added root segment has its hygienic context and may be resolved at definition site (similarly to use $crate::a::b::c from Macros 1.0) or at call site (similarly to use a::b::c in Macros 1.0).

The question is how to determine this context, given that the root segment doesn't have its own explicit token?
Without its own token the crate root needs to inherit context from some other token that really exists (compare with #50122), but what token it should be exactly is a pretty tough choice.

macro m() {
    use a::b::c; // ctxt(root) is most probably def-site because *everything* here has def-site context
    use a::$b; // ctxt(root) is probably def-site?
    use ::$a; // ctxt(root) is probably def-site??
    use $a; // ctxt(root) is ???
    $use_passed_from_the_outside a::b::c; // ctxt(root) is call-site??? should it be inherited from `use`?
    $use_passed_from_the_outside $a; // this is not even funny
}

I hope this issue doesn't affect Procedural Macros 1.2 (crossing my fingers).
Since all tokens generated and accepted by those macros are supposed to have the same call-site context, implicitly created crate roots will have the same call-site context anyway regardless of the token from which it's going to inherit it.

@nikomatsakis
Copy link
Contributor

In #50999 I was pondering similar questions.

It occurs to me that I don't entirely know what you are asking. Are you suggesting that, for use $x, the path $x might not be interpreted as an absolute path, depending on where it comes from? (e.g., if $x were foo::bar, are you suggesting that foo might be relative to the module from the caller?) I think probably not, so I'm going to assume not.

In that case, the question is whether the path is interpreted as a Rust 2015 Absolute Path (relative to crate root) or a Rust 2018 Absolute Path (relative to crate universe; i.e., begins with a crate).

I feel like this decision should be made based on the span of the first segment of the path. This is because the later segments (::b::c in a::b::c) are relative to the first. So if you had

macro_rules! m {
  ($x:ident) => {
    {
      use $x::bar;
      bar();
    }
  }
}

then if m is invoked from a Rust 2015 crate with m!(foo), then this should import crate::foo::bar. If m is invoked from a rust 2018 crate with m!(foo), then it would import foo::bar (i.e., from the crate foo). The same would, I think, be true if the macro were defined with $x:ident. (This is compatible with today's behavior)

@nikomatsakis
Copy link
Contributor

This behavior I expect from your example is as follows (note in particular that the source of the use is not relevant):

macro m() {
    use a::b::c; // "def-site" as `a` appears in `def-site`
    use a::$b; // def-site
    use ::$a; // XXX interesting example, see below
    use $a; // global path, but call-site determines relative to what
    $use_passed_from_the_outside a::b::c; // def-site
    $use_passed_from_the_outside $a; // same as `use $a`
}

However, one interesting example is use ::$x — one could consider the initial token to be ::, but one could also I suppose consider it to be $x. Put another way, one could make the case, I think, that use P and use ::P are exactly equivalent.

@petrochenkov
Copy link
Contributor Author

@nikomatsakis

It occurs to me that I don't entirely know what you are asking.

Edition hygiene wasn't implemented when this issue was created, so I meant "what crate the root refers to", i.e.

// crate a;
macro m {
    use x::y::z;
}

// crate b;
m!(); // crate_a::x::y::z or crate_b::x::y::z?

but the "crate root or universe" decision should be equivalent.

@petrochenkov
Copy link
Contributor Author

petrochenkov commented May 30, 2018

Context of the first segment looks like a quite reasonable choice.
I'd rather use the context of :: in ::$a though, i.e. treat the root as a "explicit zero-width segment right before ::".
This will give a macro author the choice to force use ::$a; into the local context, for example.

@XAMPPRocky XAMPPRocky added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 2, 2018
bors added a commit that referenced this issue Nov 25, 2018
[beta] resolve: Implement edition hygiene for imports and absolute paths

The changes in behavior of imports and absolute paths are the most significant breaking changes of 2018 edition.
However, these changes are not covered by edition hygiene, so macros defined by 2015 edition crates expanded in 2018 edition crates are still interpreted in the 2018 edition way when they contain imports or absolute paths.
This means the promise of seamless integration of crates built with different editions, including use of macros, doesn't hold fully.
This PR fixes that and implements edition hygiene for imports and absolute paths, so they behave according to the edition in which they were written, even in macros.

### Detailed rules (mostly taken from #50376)
#### Preface
We keep edition data per-span in the compiler. This means each span knows its edition.
Each identifier has a span, so each identifier knows its edition.

#### Absolute paths

Explicitly written absolute paths `::ident::...` are desugared into something like `{{root}}::ident::...` in the compiler, where `{{root}}` is also treated as an identifier.
`{{root}}` inherits its span/hygienic-context from the token `::`.

If the span is from 2015 edition, then `{{root}}` is interpreted as the current crate root (`crate::...` with same hygienic context).
If the span is from 2018 edition, then `{{root}}` is interpreted as "crate universe" (`extern::...`).

#### Imports

To resolve an import `use ident::...` we need to resolve `ident` first.
The idea in this PR is that `ident` fully knows how to resolve itself.

If `ident`'s span is from 2015 edition, then the identifier is resolved in the current crate root (effectively `crate::ident::...` where `crate` has the same hygienic context as `ident`).
If `ident`'s span is from 2018 edition, then the identifier is resolved in the current scope, without prepending anything (well, at least with uniform paths).

There's one corner case in which there's no identifier - prefix-less glob import `use *;`.
In this case the span is inherited from the token `*`.
`use *;` && `is_2015(span(*))` -> `use crate::*;` && `span(crate) == span(*)`.
`use *;` && `is_2018(span(*))` -> error.

---
Why beta:
	- Compatibility of 2015 edition crates with 2018 edition crates, including macros, is one of the main promises of the edition initiative.
	- This is technically a breaking change for 2018 edition crates or crates depending on 2018 edition crates.
	- ~This PR is based on #55884 which hasn't landed on nightly yet :)~ No longer true (#56042).

Previous attempt #50999
Closes #50376
Closes #52848
Closes #53007

r? @ghost
@petrochenkov
Copy link
Contributor Author

Closed by #56053

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros-2.0 Area: Declarative macros 2.0 (#39412) C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

3 participants