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

Cannot refer to item inside function from module inside function #79260

Open
davidhewitt opened this issue Nov 21, 2020 · 12 comments
Open

Cannot refer to item inside function from module inside function #79260

davidhewitt opened this issue Nov 21, 2020 · 12 comments
Labels
A-resolve Area: Name resolution C-bug Category: This is a bug. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@davidhewitt
Copy link
Contributor

I tried this code (https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8c1861ee7e07799c5897f37f0aa2f849):

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }
    
    fn bar () {}
}

This fails to compile:

error[E0425]: cannot find function `bar` in this scope
 --> src/main.rs:6:13
  |
6 |             bar();
  |             ^^^ not found in this scope

warning: unused import: `super::*`
 --> src/main.rs:3:13
  |
3 |         use super::*;
  |             ^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

rustc --version --verbose:

rustc 1.48.0 (7eac88abb 2020-11-16)
binary: rustc
commit-hash: 7eac88abb2e57e752f3302f02be5f3ce3d7adfb4
commit-date: 2020-11-16
host: x86_64-unknown-linux-gnu
release: 1.48.0
LLVM version: 11.0

Also reproduces on current nightly (as of 2020-11-21).


I suspect that what's going on is that use super::* doesn't actually resolve to the scope inside fn main, but instead the surrounding scope fn main is itself in.

I tried to search around other issues to find whether this is expected.

This is a super weird special case. It seems plausible (at least to me) that use super::* from a module inside a function should bring other items local to that function into scope.

@davidhewitt davidhewitt added the C-bug Category: This is a bug. label Nov 21, 2020
@davidhewitt
Copy link
Contributor Author

(If the lang team agrees that this code should compile, I'm willing to attempt a patch to fix.)

@jonas-schievink
Copy link
Contributor

Note that fixing this would break this code:

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }
}

fn bar() {}

@davidhewitt
Copy link
Contributor Author

davidhewitt commented Nov 21, 2020

I'm not convinced it would break that code; bar is already a valid identifier inside fn main and so I would argue use super::*; inside main::foo should resolve fn bar in that case too.

@davidhewitt
Copy link
Contributor Author

I can find another example of this which is easy to reproduce and seems like a much more likely case to trigger!

Write a doctest which declares a module inside it:

/// ```rust
/// struct Foo;
///
/// mod foobar {
///     use super::*;
///
///     fn bar() -> Foo { Foo }
/// }
///
/// ```
fn foo() {}

This doctest will fail to compile:

---- src/lib.rs - foo (line 1) stdout ----
error[E0412]: cannot find type `Foo` in this scope
 --> src/lib.rs:7:17
  |
8 |     fn bar() -> Foo { Foo }
  |                 ^^^ not found in this scope

error[E0425]: cannot find value `Foo` in this scope
 --> src/lib.rs:7:23
  |
8 |     fn bar() -> Foo { Foo }
  |                       ^^^ not found in this scope

I would guess that the doctest framework wraps the doctest code inside a function, so we see exactly the same issue as in my original report.

@davidhewitt
Copy link
Contributor Author

davidhewitt commented Nov 21, 2020

Ah, but I think this variation of @jonas-schievink 's example will break:

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }

    fn bar() {
        println!("inner bar");
    }
}

fn bar() {
    println!("outer bar");
}

At the moment it always prints "outer bar", but if we fixed my original example this would either be ambiguous or print "inner bar", I asssume.

(We could perhaps make it backwards compatble by letting items outside the function take precedence, and then switch the order to prefer items inside the function on an edition change.)

@jyn514 jyn514 added A-resolve Area: Name resolution T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Nov 21, 2020
@Havvy
Copy link
Contributor

Havvy commented Nov 22, 2020

I am against changing the meaning of super.

I also wouldn't call this a bug. Everything is working as it originally is intended to. super refers to the parent module, not the parent item scope. Functions allow defining items that are local to the function and a module is non-local, even if it's declared inside the function.

IMO, if you need a module, just like if you need an impl, it should not be defined in a function.

@petrochenkov
Copy link
Contributor

We probably need some way to refer to block-that-are-parent-of-modules, but super is not that way.
super has a well-defined meaning - parent mod, similarly to how self is the current module and not current block or something.

Besides being a breaking change, path resolving to an unnamed block is some new possibility that will cause a number of subsequent issues.
What would happen here, for example

use super as foo; // referes to a block
let bar = foo;

@petrochenkov
Copy link
Contributor

petrochenkov commented Nov 22, 2020

My preferred solution to this issue is probably #[transparent] mod foo { ... } that prevent the module from introducing a "name barrier".
It's something that is more useful in various situations, and fits more naturally into the existing system.

UPD: Example.

struct S;

#[transparent]
mod m {
    fn f() {
        let s = S; // OK
    }
}

@davidhewitt
Copy link
Contributor Author

#[transparent] attribute is a very compelling proposal 👍. It would be a shame that it would be needed in some doctest examples. Still great as a 100% backwards-compatible opt-in design.

I wrote a PR to experiment with what I proposed above (#79309) - I've added an alternatives section with #[transparent].

@AndreySmirnov81
Copy link

I would like to see this result:

1. For Rust before 2024 edition

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar(); // <- error[E0425]: cannot find function `bar` in this scope
        }
    }

    fn bar() {
        println!("inner bar");
    }
}

error[E0425]: cannot find function bar in this scope

2. For Rust before 2024 edition

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }

    fn bar() {
        println!("inner bar");
    }
}

fn bar() {
    println!("outer bar");
}

outer bar

3. For Rust before 2024 edition

fn main() {
    #[enable_fn_scope_from_inner_module] // <- !!!
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }

    fn bar() {
        println!("inner bar");
    }
}

fn bar() {
    println!("outer bar");
}

inner bar

4. For Rust before 2024 edition

#![enable_fn_scope_from_inner_module] // <- !!!

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }

    fn bar() {
        println!("inner bar");
    }
}

fn bar() {
    println!("outer bar");
}

inner bar

5. For Rust 2024 edition

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }

    fn bar() { // <- obscures the outer bar
        println!("inner bar");
    }
}

fn bar() {
    println!("outer bar");
}

inner bar

@AndreySmirnov81
Copy link

Alternatively, for this non-working example:

изображение

we can use this:

изображение

However, there were difficulties with rust-analyzer: rust-lang/rust-analyzer#15805

@Veetaha
Copy link
Contributor

Veetaha commented Aug 11, 2024

Here is a real use case for this. I'd like to generate a child module in a proc macro where I'd place the definition of the builder stuct (for privacy of its fields). The builder's fields should reference types from the surrounding scope.

fn example() {
    struct Password(String);

    // Suppose a `#[builder]` proc macro was placed on this struct
    struct User {
        password: Password,
    }

    // This module is generated by the proc-macro
    mod user_builder {                  
        use super::*;                   

        pub(super) struct UserBuilder { 
            password: Option<Password>, 
        }                               
    }                                   
}

I made an article about this problem here: https://elastio.github.io/bon/blog/the-weird-of-function-local-types-in-rust

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-resolve Area: Name resolution C-bug Category: This is a bug. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants