Only the first expression in an include!d file is included.

3439491
Opened by Emilio Cobos Álvarez at 2021-07-14 04:34:19

If you have a file that invokes a macro and you include! it, the following happens:

main.rs

fn main() {
    macro_rules! my_macro {
        ($name:expr) => {{
            printn!("{}", $name);
        }}
    }

    include!("helper.rs");
}

helper.rs

my_macro!("foo");
my_macro!("bar");

STR

$ rustc main.rs
$ ./main
foo

Expected output

Should print foo and bar, just as if the code would have been inlined.

Tested with latest nightly and stable:

rustc 1.12.0-nightly (080e0e072 2016-08-08)
rustc 1.10.0 (cfcb716cf 2016-07-03)
  1. This is unrelated to the macros used in the included file, the following exhibits the same problem:

    main.rs:

    fn print(s: &str) { println!("{}", s); }
    
    fn main() {
        include!("helper.rs");
    }
    

    helper.rs:

    print("foo");
    print("bar");
    print("foobar");
    

    It looks like the issue is rather that include! inside of a function expands to the first statement only.

    Wrapping the contents of helper.rs in braces prints all three lines.

    Also note that the documentation of include! says:

    Parse the current given file as an expression.

    Thus the current behaviour is probably as documented.

    Tim Neumann at 2016-08-10 15:31:34

  2. But the contents of helper.rs aren't a valid expression, it's only valid as a list of statements. Maybe it's parsed as an expression and the remainder is thrown away (like macros used to do)?

    Jonas Schievink at 2016-08-10 15:44:15

  3. Oh I see. If file contents are thrown away though, I think at least a warning would come handy, but yeah, I see now, thanks!

    Emilio Cobos Álvarez at 2016-08-10 16:09:39

  4. Someone reported a similar experience on users, trying to use [include!("path")] to populate an array.

    While it doesn't surprise me that the result is limited to a single expression or statement, it seems like there ought to be an error message?

    Michael Lamparski at 2017-01-17 23:16:56

  5. Still happens, just ran into this today. Braces can be used as a workaround.

    {
    print("foo");
    print("bar");
    print("foobar");
    }
    

    cc @jseyfried @nrc

    est31 at 2018-01-03 13:43:11

  6. While it doesn't surprise me that the result is limited to a single expression or statement, it seems like there ought to be an error message?

    The compiler now gives an error if there are multiple expressions in the included file:

    error: include macro expected single expression in source
     --> helper.rs:1:16
      |
    1 | println!("foo");
      |                ^
      |
      = note: `#[deny(incomplete_include)]` on by default
    

    (presumably it's a deny-by-default lint and not a hard error for backwards-compatibility reasons.)

    jyn at 2021-07-14 03:52:16

  7. @jyn514 seems that there is still a decision to be made whether to turn this into a hard error or so: #64284

    est31 at 2021-07-14 03:57:32

  8. I mean, I personally don't think we should be making things hard errors unless there's a specific reason to, the only benefit is that it slightly simplifies the compiler. I guess it doesn't hurt to have an issue open; but I would rather bring this up in a T-lang meeting and have a decision one way or another than leave this open indefinitely.

    jyn at 2021-07-14 04:07:54

  9. @jyn514 yeah if the decision by the lang team is to keep it a deny by default lint indefinitely, it's okay too. As you've rightly pointed out, errors should be introduced cautiously to previously compiling code. There should be a decision on it, though, because there are possible other resolutions.

    For example, this works right now and prints hello 1, hello 2:

    fn main() {
        macro_rules! my_macro {
            () => {
                println!("hello 1");
                println!("hello 2");
            }
        }
    
        my_macro!();
    }
    

    So why can't the include macro follow this behaviour? Doing so would be a breaking change at this point, but maybe the new behaviour could be introduced at an edition boundary. Note that this doesn't work though:

    fn main() {
        macro_rules! my_macro {
            () => {
                1,2,3 //~ERROR macro expansion ignores token `,` and any following
            }
        }
    
        let v = [my_macro!()];
        println!("{:?}", v);
    }
    

    est31 at 2021-07-14 04:28:02