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

-Zmacro-backtrace gets confused when macros define other macros #92180

Open
jyn514 opened this issue Dec 21, 2021 · 0 comments
Open

-Zmacro-backtrace gets confused when macros define other macros #92180

jyn514 opened this issue Dec 21, 2021 · 0 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@jyn514
Copy link
Member

jyn514 commented Dec 21, 2021

Given the following code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=49b4f59c6307c22b5f5705f3bbf48205

#![crate_type = "lib"]

macro_rules! define_state_group {
    ( $name:ident = { $($states:tt)+ } ) => {
        macro_rules! $name {
            () => {
                state!($($states)+);
            };
        }
    };
}

macro_rules! state {
    (
        $name:ident $(<-- ( $($enter_actions:tt)* ))* {
            $($arms:tt)*
        }

        $($rest:tt)*
    ) => {
        fn $name(&mut self, input: &[u8]) -> StateResult {
            loop {
                let ch = self.consume_ch(input);

                state_body!(|[self, input, ch]|> [$($arms)*], [$($($enter_actions)*)*]);
            }
        }

        state!($($rest)*);
    };

    // NOTE: end of the state list
    () => ();
}

macro_rules! state_body {
    ( | [ $self:tt, $input:ident, $ch:ident ] |> [$($arms:tt)+], [$($enter_actions:tt)*] ) => {
        action_list!(@state_enter |$self, $input|> $($enter_actions)*);
        state_body!(@map_arms | [$self, $input, $ch] |> [$($arms)+], [])
    };


    // Recursively expand each arm's pattern
    //--------------------------------------------------------------------
    ( @map_arms
        | $scope_vars:tt |>
        [ $pat:tt => ( $($actions:tt)* ) $($rest:tt)* ], [ $($expanded:tt)* ]
    ) => {
        arm_pattern!(|[ $scope_vars, [$($rest)*], [$($expanded)*] ]|> $pat => ( $($actions)* ))
    };

    ( @map_arms
        | $scope_vars:tt |>
        [], [$($expanded:tt)*]
    ) => {
        state_body!(@match_block |$scope_vars|> $($expanded)*);
    };


    // Callback for the expand_arm_pattern
    //--------------------------------------------------------------------
    ( @callback
        | [ $scope_vars:tt, [$($pending:tt)*], [$($expanded:tt)*] ] |>
        $($expanded_arm:tt)*
    ) => {
        state_body!(@map_arms | $scope_vars |> [$($pending)*], [$($expanded)* $($expanded_arm)*])
    };


    // Character match block
    //--------------------------------------------------------------------
    ( @match_block
        | [ $self:tt, $input:ident, $ch:ident ] |>
        $( $pat:pat $(|$pat_cont:pat)* $(if $pat_expr:expr)* => ( $($actions:tt)* ) )*
    ) => {
        // NOTE: guard against unreachable patterns
        // (e.g. such may occur if `eof => ...` arm comes before `eoc => ...` arm.)
        #[deny(unreachable_patterns)]
        match $ch {
            $(
                $pat $(| $pat_cont)* $(if $pat_expr)* => {
                    action_list!(|$self, $input|> $($actions)*);
                }
            )*
        }
    };
}

macro_rules! action_list {
    ( | $self:tt, $input:ident |>
        if $cond:ident
            ( $($if_actions:tt)* )
        else
            ( $($else_actions:tt)* )
    ) => {
        if $self.$cond() {
            action_list!(| $self, $input |> $($if_actions)*);
        } else {
            action_list!(| $self, $input |> $($else_actions)*);
        }
    };

    ( | $self:tt, $input:ident |> { $($code_block:tt)* } ) => ( $($code_block)* );

    ( | $self:tt, $input:ident |> $action:ident $($args:expr),*; $($rest:tt)* ) => {
        trace!(@actions $action $($args:expr)*);
        action!(| $self, $input |> $action $($args),*);
        action_list!(| $self, $input |> $($rest)*);
    };

     ( | $self:tt, $input:ident |> $action:ident ? $($args:expr),*; $($rest:tt)* ) => {
        trace!(@actions $action $($args:expr)*);
        action!(| $self, $input |> $action ? $($args),*);
        action_list!(| $self, $input |> $($rest)*);
    };

    // NOTE: state transition should always be in the end of the action list
    ( | $self:tt, $input:ident|> $($transition:tt)+ ) => {
        trace!(@actions $($transition)+);
        action!(@state_transition | $self, $input |> $($transition)+);
    };

    // NOTE: end of the action list
    ( | $self:tt, $input:ident |> ) => ();


    // State enter action list
    //--------------------------------------------------------------------
    ( @state_enter | $self:tt, $input:ident |> $($actions:tt)+ ) => {
        if $self.is_state_enter() {
            action_list!(|$self, $input|> $($actions)*);
            $self.set_is_state_enter(false);
        }
    };

    // NOTE: don't generate any code for the empty action list
    ( @state_enter | $self:tt, $input:ident |> ) => ();
}

define_state_group!(rawtext_states_group = {

    rawtext_state {
        b'<' => ( emit_text?; mark_tag_start; --> rawtext_less_than_sign_state )
        eoc  => ( emit_text?; )
        eof  => ( emit_text?; emit_eof?; )
        _    => ()
    }

    rawtext_less_than_sign_state {
        b'/' => ( --> rawtext_end_tag_open_state )
        eof  => ( emit_text?; emit_eof?; )
        _    => ( unmark_tag_start; emit_text?; reconsume in rawtext_state )
    }

    rawtext_end_tag_open_state {
        alpha => ( create_end_tag; start_token_part; update_tag_name_hash; --> rawtext_end_tag_name_state )
        eof   => ( emit_text?; emit_eof?; )
        _     => ( unmark_tag_start; emit_text?; reconsume in rawtext_state )
    }

    rawtext_end_tag_name_state {
        whitespace => (
            if is_appropriate_end_tag
                ( finish_tag_name?; --> before_attribute_name_state )
            else
                ( unmark_tag_start; emit_text?; reconsume in rawtext_state )
        )

        b'/' => (
            if is_appropriate_end_tag
                ( --> self_closing_start_tag_state )
            else
                ( unmark_tag_start; emit_text?; reconsume in rawtext_state )
        )

        b'>' => (
            if is_appropriate_end_tag
                ( emit_tag?; --> dyn next_text_parsing_state )
            else
                ( unmark_tag_start; emit_text?; reconsume in rawtext_state )
        )

        alpha => ( update_tag_name_hash; )
        eof   => ( emit_text?; emit_eof?; )
        _     => ( unmark_tag_start; emit_text?; reconsume in rawtext_state )
    }

});

// #[macro_use]
// mod ch_sequence;

macro_rules! arm_pattern {
    ( | $cb_args:tt |>
         alpha => $actions:tt
    ) => {
        state_body!(@callback | $cb_args |> Some(b'a'..=b'z') | Some(b'A'..=b'Z') => $actions);
    };

    ( | $cb_args:tt |>
        whitespace => $actions:tt
    ) => {
        state_body!(@callback | $cb_args |>
            Some(b' ') | Some(b'\n') | Some(b'\r') | Some(b'\t') | Some(b'\x0C') => $actions
        );
    };

    ( | [ [$self:tt, $input_chunk:ident, $ch:ident ], $($rest_cb_args:tt)+ ] |>
        closing_quote => $actions:tt
    ) => {
        state_body!(@callback | [ [$self, $input_chunk, $ch], $($rest_cb_args)+ ] |>
            Some(ch) if ch == $self.closing_quote() => $actions
        );
    };


    ( | [ [$self:tt, $input:ident, $ch:ident ], $($rest_cb_args:tt)+ ] |>
        eoc => ( $($actions:tt)* )
    ) => {
        state_body!(@callback | [ [$self, $input, $ch], $($rest_cb_args)+ ] |>
            None if !$self.is_last_input() => ({
                action_list!(|$self, $input|> $($actions)* );

                return $self.break_on_end_of_input($input);
            })
        );
    };

    // NOTE: this arm is always enforced by the compiler to make match exhaustive,
    // so it's safe to break parsing loop here, since we don't have any input left
    // to parse. We execute EOF actions only if it's a last input, otherwise we just
    // break the parsing loop if it hasn't been done by the explicit EOC arm.
    ( | [ [$self:tt, $input:ident, $ch:ident ], $($rest_cb_args:tt)+ ] |>
        eof => ( $($actions:tt)* )
    ) => {
        state_body!(@callback | [ [$self, $input, $ch], $($rest_cb_args)+ ] |>
            None => ({
                if $self.is_last_input() {
                    action_list!(|$self, $input|> $($actions)* );
                }

                return $self.break_on_end_of_input($input);
            })
        );
    };

    ( | [ $scope_vars:tt, $($rest_cb_args:tt)+ ] |>
        [ $seq_pat:tt $(; $case_mod:ident)* ] => $actions:tt
    ) => {
        // NOTE: character sequence arm should be expanded in
        // place before we hit the character match block.
        ch_sequence_arm_pattern!(|$scope_vars|> $seq_pat, $actions, $($case_mod)* );
        state_body!(@callback | [ $scope_vars, $($rest_cb_args)+ ] |>);
    };

    ( | $cb_args:tt |> $pat:pat => $actions:tt ) => {
        state_body!(@callback | $cb_args |> Some($pat) => $actions);
    };
}

macro_rules! action {
    (| $self:tt, $input:ident | > $action_fn:ident ? $($args:expr),* ) => {
        $self.$action_fn($input $(,$args),*).map_err(ParsingTermination::ActionError)?;
    };

    (| $self:tt, $input:ident | > $action_fn:ident $($args:expr),* ) => {
        $self.$action_fn($input $(,$args),*);
    };

    ( @state_transition | $self:tt, $input:ident | > reconsume in $state:ident) => {
        $self.unconsume_ch();
        action!(@state_transition | $self, $input | > --> $state);
    };

    ( @state_transition | $self:tt, $input:ident | > - -> $state:ident) => {
        $self.switch_state(Self::$state);
        return Ok(());
    };

    ( @state_transition | $self:tt, $input:ident | > - -> dyn $state_getter:ident) => {
        {
            let state = $self.$state_getter();
            $self.switch_state(state);
        }

        return Ok(());
    };
}

macro_rules! trace {
    ( $($args:tt)+ ) => {
    }
}

use std::error::Error;

type StateResult = Result<(), ParsingTermination>;
type ActionResult = Result<(), Box<dyn Error>>;
// type ActionResult = Result<(), Box<dyn Error>>;
struct Parser;
type State = fn(&mut Parser, &[u8]) -> StateResult;
enum ParsingTermination {
    ActionError(Box<dyn Error>)
}

impl Parser {
    rawtext_states_group!();

    fn consume_ch(&mut self, input: &[u8]) -> Option<u8> {
        loop {}
    }
    fn unconsume_ch(&mut self) {}
    fn break_on_end_of_input(&mut self, input: &[u8]) -> StateResult {
        loop {}
    }
    fn is_last_input(&mut self) -> bool {
        loop {}
    }
    fn is_appropriate_end_tag(&mut self) -> bool {
        loop {}
    }
    fn switch_state(&mut self, state: State) {}
    fn emit_text(&mut self, input: &[u8]) -> ActionResult {
        loop {}
    }
    fn unmark_tag_start(&mut self, _input: &[u8]) {}
    fn emit_eof(&mut self, input: &[u8]) -> ActionResult {
        loop {}
    }
    fn update_tag_name_hash(&mut self, input: &[u8]) {}
    fn emit_tag(&mut self, _input: &[u8]) -> ActionResult {
        loop {}
    }
    fn next_text_parsing_state(&self) -> fn(&mut Self, &[u8]) -> StateResult {
        loop {}
    }
    fn finish_tag_name<S>(&mut self, input: &[u8], tag_hint_sink: S) -> ActionResult {
        loop {}
    }
    fn self_closing_start_tag_state(&mut self, input: &[u8]) -> StateResult {
        loop {}
    }
    fn before_attribute_name_state(&mut self, input: &[u8]) -> StateResult {
        loop {}
    }
    fn start_token_part(&mut self, input: &[u8]) -> StateResult {
        loop {}
    }
    fn create_end_tag(&mut self, _input: &[u8]) {
        loop {}
    }
    fn mark_tag_start(&mut self, input: &[u8]) {
        loop {}
    }
}

The current output with -Zmacro-backtrace=yes:

error[E0061]: this function takes 2 arguments but 1 argument was supplied
   --> src/parser/state_machine/syntax_dsl/backtrace.rs:164:19
    |
5   | /         macro_rules! $name {
6   | |             () => {
7   | |                 state!($($states)+);
8   | |             };
9   | |         }
    | |_________- in this expansion of `rawtext_states_group!`
...
164 |                   ( finish_tag_name?; --> before_attribute_name_state )
    |                     ^^^^^^^^^^^^^^^ expected 2 arguments
...
263 |           $self.$action_fn($input $(,$args),*).map_err(ParsingTermination::ActionErr...
    |                            ------ supplied 1 argument
...
307 |       rawtext_states_group!();
    |       ----------------------- in this macro invocation
    |
note: associated function defined here
   --> src/parser/state_machine/syntax_dsl/backtrace.rs:337:8
    |
337 |     fn finish_tag_name<S>(&mut self, input: &[u8], tag_hint_sink: S) -> ActionResult {
    |        ^^^^^^^^^^^^^^^    ---------  ------------  ----------------=

Ideally the output should include at least the expansion of state! and state_body!; right now it only shows define_state_group!, rawtext_states_group!, and action!.

(I'll try to reduce this, but I only have so much time to work on this.)

cc @estebank

@jyn514 jyn514 added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example labels Dec 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example 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

1 participant