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

Paths involving 'super' don't work from within a function #64079

Open
Aaron1011 opened this issue Sep 1, 2019 · 7 comments

Comments

@Aaron1011
Copy link
Contributor

commented Sep 1, 2019

The following code compiles correctly:

struct Foo;
mod inner {
    use super::Foo;
}

However, when wrapped in a function:

fn outer() {
    struct Foo;
    mod inner {
        use super::Foo;
    }
}

it gives the following error:

error[E0432]: unresolved import `super::Foo`
 --> src/lib.rs:4:13
  |
4 |         use super::Foo;
  |             ^^^^^^^^^^ no `Foo` in the root

error: aborting due to previous error

This can make it difficult to write attribute macros which expand to module declarations. Ideally, such macros would be useeable no matter where the annotated type is declared. However, this issue prevents such macros from being used on a type declared within a function.

@Aaron1011

This comment has been minimized.

Copy link
Contributor Author

commented Sep 1, 2019

Getting this to work in a backward-compatible way is going to be tricky.
Code like this needs to continue to work:

struct Foo;
fn my_fn() {
   use self::Foo;
}

mod bar {
    fn other_fn() {
        use super::Foo;
    }
}

The naive approach of treating functions as normal module scopes would break both of these snippets.

I think this is going to require an RFC to add a new type of path qualifier. Something like use fn::super::Foo could be used to opt-in to treating functions as first-class paths (though they still can't be explicitly named).

I'll write up a pre-RFC and post it on internals.

@petrochenkov

This comment has been minimized.

Copy link
Contributor

commented Sep 1, 2019

So, this is indeed a missing piece in our name resolution story.

The solution I imagined is introduction of transparent modules that

  1. are actual modules, so they have associated self, super, can be used in path prefixes, etc, but
  2. do not act as barriers for names, e.g. names that are in scope at
    #[transparent]
    mod m { ... }
    definition point are in scope inside the module as well.

This seems to be a feature with a much wider range of applications than special path syntax extensions like fn::super::foo or something.

@petrochenkov

This comment has been minimized.

Copy link
Contributor

commented Sep 1, 2019

Here's an example in which we generate a module, pull selected names into it, but provide isolation from any other names in scope.

fn f() {
    struct FromScopeGood;
    struct FromScopeBad;

    #[transparent]
    mod helper { // Gives access to selected names
        use FromScopeGood;
        
        mod useful { // Provides isolation from any other names
            type A = super::FromScopeGood; // OK
            type B = FromScopeBad; // ERROR
        }
    }
}
@petrochenkov

This comment has been minimized.

Copy link
Contributor

commented Sep 2, 2019

If isolation is not specifically required, then it's better to generate const _: () = { /* contents */ } rather than a module, then everything is in scope and you don't need super.

@Aaron1011

This comment has been minimized.

Copy link
Contributor Author

commented Sep 2, 2019

@petrochenkov: I don't think that a #[transparent] module would be the proper solution for this issue.

In my view, the root issue here is that functions create a strange kind of scope that hides declarations from its children.

I would expect the following two snippets to have equivalent behavior:

struct TopLevelStruct;
mod outer {
    struct OuterStruct;
    mod inner {
        use super::OuterStruct;
        use super::super::TopLevelStruct;
    }
}
struct TopLevelStruct;
fn outer() {
    struct OuterStruct;
    fn inner() {
        use super::OuterStruct;
        use super::super::TopLevelStruct;
    }
}

However, the second snippet does not compile. Since OuterStruct is declared within the function outer, it cannot be named by any child modules/scopes of outer. This strikes me as very inconsistent, and makes writing macros more difficult.

I think it's important for this snippet of code to be able to compile:

struct SomeType; // Either declared or imported one scope above 'inner'
mod inner {
    use super::SomeType;
}

That is, it should be possible to declare a module and import items from the scope/module immediately above the new module. This is orthogonal to any #[transparent] attribute - it's useful to be able to import things independent from the type of module you're creating. In particular, not having this means that it's impossible for certain attribute macros to be used on types declared within a function. This makes using such macros unnecessarily cumbersome, requiring users to make unwanted structural changes to their code.

I think adding a new path qualifier is ugly, but it's the only way I can think of to preserve backwards compatibility. In a future edition, it would be possible to make functions behave like anonymous modules in terms of import resolution. That is:

struct TopLevelStruct;
fn outer() {
    struct OuterStruct;
    fn inner() {
        use super::OuterStruct;
        use super::super::TopLevelStruct;
    }
}

would compile, but it would still be impossible to import items declared in fn outer() from outside fn outer(). It could even be possible for cargo fix to automatically convert old code by inserting an extra super in fron tof any non-global use statements in a function.

Obviously, this would require an RFC. However, I think there's a lot of value in making use statements more consistent.

@Aaron1011

This comment has been minimized.

Copy link
Contributor Author

commented Sep 2, 2019

@petrochenkov:

Also, isn't a #[transparent] module just equivalent to use fn::super::* within a normal module? E.g.

#[transparent]
mod foo {
}

is the same as

mod foo {
    use fn::super::*;
}
@Aaron1011

This comment has been minimized.

Copy link
Contributor Author

commented Sep 2, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.