From 248dfe7368ea113cd96b49f12b2d78ee8c77cd5a Mon Sep 17 00:00:00 2001 From: Boxy Date: Sun, 15 Sep 2024 21:21:31 +0100 Subject: [PATCH] Handle macro calls in anon const def creation take 2 --- compiler/rustc_ast/src/ast.rs | 11 +- compiler/rustc_ast_lowering/src/asm.rs | 2 +- compiler/rustc_ast_lowering/src/expr.rs | 2 +- compiler/rustc_ast_lowering/src/lib.rs | 2 +- compiler/rustc_resolve/src/def_collector.rs | 102 ++++++++++++++---- compiler/rustc_resolve/src/late.rs | 4 +- compiler/rustc_resolve/src/lib.rs | 5 + ...rivial-const-arg-macro-braced-expansion.rs | 14 +++ ...al-const-arg-macro-braced-expansion.stderr | 14 +++ .../trivial-const-arg-macro-nested-braces.rs | 15 +++ ...ivial-const-arg-macro-nested-braces.stderr | 15 +++ .../early/trivial-const-arg-nested-braces.rs | 9 ++ .../trivial-const-arg-nested-braces.stderr | 11 ++ 13 files changed, 178 insertions(+), 28 deletions(-) create mode 100644 tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.rs create mode 100644 tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.stderr create mode 100644 tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.rs create mode 100644 tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.stderr create mode 100644 tests/ui/const-generics/early/trivial-const-arg-nested-braces.rs create mode 100644 tests/ui/const-generics/early/trivial-const-arg-nested-braces.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index f433f2eca1a80..4e6601f22970e 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1187,8 +1187,8 @@ impl Expr { /// `min_const_generics` as more complex expressions are not supported. /// /// Does not ensure that the path resolves to a const param, the caller should check this. - pub fn is_potential_trivial_const_arg(&self) -> bool { - let this = self.maybe_unwrap_block(); + pub fn is_potential_trivial_const_arg(&self, strip_identity_block: bool) -> bool { + let this = if strip_identity_block { self.maybe_unwrap_block().1 } else { self }; if let ExprKind::Path(None, path) = &this.kind && path.is_potential_trivial_const_arg() @@ -1199,14 +1199,15 @@ impl Expr { } } - pub fn maybe_unwrap_block(&self) -> &Expr { + /// Returns an expression with (when possible) *one* outter brace removed + pub fn maybe_unwrap_block(&self) -> (bool, &Expr) { if let ExprKind::Block(block, None) = &self.kind && let [stmt] = block.stmts.as_slice() && let StmtKind::Expr(expr) = &stmt.kind { - expr + (true, expr) } else { - self + (false, self) } } diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs index a9d1ee5c9c13a..f5745d97db71f 100644 --- a/compiler/rustc_ast_lowering/src/asm.rs +++ b/compiler/rustc_ast_lowering/src/asm.rs @@ -220,7 +220,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let parent_def_id = self.current_def_id_parent; let node_id = self.next_node_id(); // HACK(min_generic_const_args): see lower_anon_const - if !expr.is_potential_trivial_const_arg() { + if !expr.is_potential_trivial_const_arg(true) { self.create_def( parent_def_id, node_id, diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index e105026ebd19d..137197c45640d 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -387,7 +387,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let node_id = self.next_node_id(); // HACK(min_generic_const_args): see lower_anon_const - if !arg.is_potential_trivial_const_arg() { + if !arg.is_potential_trivial_const_arg(true) { // Add a definition for the in-band const def. self.create_def(parent_def_id, node_id, kw::Empty, DefKind::AnonConst, f.span); } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index efd3ae336afb8..7f94a7a359245 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -2441,7 +2441,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { /// See [`hir::ConstArg`] for when to use this function vs /// [`Self::lower_anon_const_to_const_arg`]. fn lower_anon_const_to_anon_const(&mut self, c: &AnonConst) -> &'hir hir::AnonConst { - if c.value.is_potential_trivial_const_arg() { + if c.value.is_potential_trivial_const_arg(true) { // HACK(min_generic_const_args): see DefCollector::visit_anon_const // Over there, we guess if this is a bare param and only create a def if // we think it's not. However we may can guess wrong (see there for example) diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 6458c88843117..ff5d1c5f9e9b6 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -138,6 +138,59 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { ); assert!(old_parent.is_none(), "parent `LocalDefId` is reset for an invocation"); } + + /// Determines whether the const argument `AnonConst` is a simple macro call, optionally + /// surrounded with braces. + /// + /// If this const argument *is* a trivial macro call then the id for the macro call is + /// returned along with the information required to build the anon const's def if + /// the macro call expands to a non-trivial expression. + fn is_const_arg_trivial_macro_expansion( + &self, + anon_const: &'a AnonConst, + ) -> Option<(PendingAnonConstInfo, NodeId)> { + let (block_was_stripped, expr) = anon_const.value.maybe_unwrap_block(); + match expr { + Expr { kind: ExprKind::MacCall(..), id, .. } => Some(( + PendingAnonConstInfo { + id: anon_const.id, + span: anon_const.value.span, + block_was_stripped, + }, + *id, + )), + _ => None, + } + } + + /// Determines whether the expression `const_arg_sub_expr` is a simple macro call, sometimes + /// surrounded with braces if a set of braces has not already been entered. This is required + /// as `{ N }` is legal rust wheras `{{ N }}` is not. + /// + /// If this expression is a trivial macro call then the id for the macro call is + /// returned along with the information required to build the anon const's def if + /// the macro call expands to a non-trivial expression. + fn is_const_arg_sub_expr_trivial_macro_expansion( + &self, + const_arg_sub_expr: &'a Expr, + ) -> Option<(PendingAnonConstInfo, NodeId)> { + let pending_anon = self.pending_anon_const_info.expect( + "Checking expr is trivial macro call without having entered anon const: `{:?}`", + ); + + let (block_was_stripped, expr) = if pending_anon.block_was_stripped { + (true, const_arg_sub_expr) + } else { + const_arg_sub_expr.maybe_unwrap_block() + }; + + match expr { + Expr { kind: ExprKind::MacCall(..), id, .. } => { + Some((PendingAnonConstInfo { block_was_stripped, ..pending_anon }, *id)) + } + _ => None, + } + } } impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { @@ -354,12 +407,12 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { // items will be messed up, but that's ok because there can't be any if we're just looking // for bare idents. - if matches!(constant.value.maybe_unwrap_block().kind, ExprKind::MacCall(..)) { - // See self.pending_anon_const_info for explanation - self.pending_anon_const_info = - Some(PendingAnonConstInfo { id: constant.id, span: constant.value.span }); - return visit::walk_anon_const(self, constant); - } else if constant.value.is_potential_trivial_const_arg() { + if let Some((pending_anon, macro_invoc)) = + self.is_const_arg_trivial_macro_expansion(constant) + { + self.pending_anon_const_info = Some(pending_anon); + return self.visit_macro_invoc(macro_invoc); + } else if constant.value.is_potential_trivial_const_arg(true) { return visit::walk_anon_const(self, constant); } @@ -368,23 +421,36 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { } fn visit_expr(&mut self, expr: &'a Expr) { - if matches!(expr.kind, ExprKind::MacCall(..)) { - return self.visit_macro_invoc(expr.id); + // If we're visiting the expression of a const argument that was a macro call then + // check if it is *still* unknown whether it is a trivial const arg or not. If so + // recurse into the macro call and delay creating the anon const def until expansion. + if self.pending_anon_const_info.is_some() + && let Some((pending_anon, macro_invoc)) = + self.is_const_arg_sub_expr_trivial_macro_expansion(expr) + { + self.pending_anon_const_info = Some(pending_anon); + return self.visit_macro_invoc(macro_invoc); } - let grandparent_def = if let Some(pending_anon) = self.pending_anon_const_info.take() { - // See self.pending_anon_const_info for explanation - if !expr.is_potential_trivial_const_arg() { + // See self.pending_anon_const_info for explanation + let parent_def = self + .pending_anon_const_info + .take() + // If we already stripped away a set of braces then do not do it again when determining + // if the macro expanded to a trivial const arg. This arises in cases such as: + // `Foo<{ bar!() }>` where `bar!()` expands to `{ N }`. This should not be considered a + // trivial const argument even though `{ N }` by itself *is*. + .filter(|pending_anon| { + !expr.is_potential_trivial_const_arg(!pending_anon.block_was_stripped) + }) + .map(|pending_anon| { self.create_def(pending_anon.id, kw::Empty, DefKind::AnonConst, pending_anon.span) - } else { - self.parent_def - } - } else { - self.parent_def - }; + }) + .unwrap_or(self.parent_def); - self.with_parent(grandparent_def, |this| { + self.with_parent(parent_def, |this| { let parent_def = match expr.kind { + ExprKind::MacCall(..) => return this.visit_macro_invoc(expr.id), ExprKind::Closure(..) | ExprKind::Gen(..) => { this.create_def(expr.id, kw::Empty, DefKind::Closure, expr.span) } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 0aa351cad4043..fcde137d1471b 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -4485,7 +4485,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ); self.resolve_anon_const_manual( - constant.value.is_potential_trivial_const_arg(), + constant.value.is_potential_trivial_const_arg(true), anon_const_kind, |this| this.resolve_expr(&constant.value, None), ) @@ -4649,7 +4649,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { // that is how they will be later lowered to HIR. if const_args.contains(&idx) { self.resolve_anon_const_manual( - argument.is_potential_trivial_const_arg(), + argument.is_potential_trivial_const_arg(true), AnonConstKind::ConstArg(IsRepeatExpr::No), |this| this.resolve_expr(argument, None), ); diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index c05bd9e72eafa..3bb1f6b52a76a 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -190,6 +190,11 @@ impl InvocationParent { #[derive(Copy, Debug, Clone)] struct PendingAnonConstInfo { + // A const arg is only a "trivial" const arg if it has at *most* one set of braces + // around the argument. We track whether we have stripped an outter brace so that + // if a macro expands to a braced expression *and* the macro was itself inside of + // some braces then we can consider it to be a non-trivial const argument. + block_was_stripped: bool, id: NodeId, span: Span, } diff --git a/tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.rs b/tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.rs new file mode 100644 index 0000000000000..33630205369f0 --- /dev/null +++ b/tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.rs @@ -0,0 +1,14 @@ +macro_rules! y { + () => { + N + }; +} + +struct A; + +fn foo() -> A<{ y!() }> { + A::<1> + //~^ ERROR: mismatched types +} + +fn main() {} diff --git a/tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.stderr b/tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.stderr new file mode 100644 index 0000000000000..4461477f3e9b7 --- /dev/null +++ b/tests/ui/const-generics/early/trivial-const-arg-macro-braced-expansion.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> $DIR/trivial-const-arg-macro-braced-expansion.rs:10:5 + | +LL | fn foo() -> A<{ y!() }> { + | ----------- expected `A` because of return type +LL | A::<1> + | ^^^^^^ expected `N`, found `1` + | + = note: expected struct `A` + found struct `A<1>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.rs b/tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.rs new file mode 100644 index 0000000000000..45c0768dde44a --- /dev/null +++ b/tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.rs @@ -0,0 +1,15 @@ +#[rustfmt::skip] +macro_rules! y { + () => { + { N } + //~^ ERROR: generic parameters may not be used in const operations + }; +} + +struct A; + +fn foo() -> A<{ y!() }> { + A::<1> +} + +fn main() {} diff --git a/tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.stderr b/tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.stderr new file mode 100644 index 0000000000000..b91d6c7a024a3 --- /dev/null +++ b/tests/ui/const-generics/early/trivial-const-arg-macro-nested-braces.stderr @@ -0,0 +1,15 @@ +error: generic parameters may not be used in const operations + --> $DIR/trivial-const-arg-macro-nested-braces.rs:4:11 + | +LL | { N } + | ^ cannot perform const operation using `N` +... +LL | fn foo() -> A<{ y!() }> { + | ---- in this macro invocation + | + = help: const parameters may only be used as standalone arguments, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + = note: this error originates in the macro `y` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + diff --git a/tests/ui/const-generics/early/trivial-const-arg-nested-braces.rs b/tests/ui/const-generics/early/trivial-const-arg-nested-braces.rs new file mode 100644 index 0000000000000..941ba6bfea785 --- /dev/null +++ b/tests/ui/const-generics/early/trivial-const-arg-nested-braces.rs @@ -0,0 +1,9 @@ +struct A; + +#[rustfmt::skip] +fn foo() -> A<{ { N } }> { + //~^ ERROR: generic parameters may not be used in const operations + A::<1> +} + +fn main() {} diff --git a/tests/ui/const-generics/early/trivial-const-arg-nested-braces.stderr b/tests/ui/const-generics/early/trivial-const-arg-nested-braces.stderr new file mode 100644 index 0000000000000..d60516ba4bcd1 --- /dev/null +++ b/tests/ui/const-generics/early/trivial-const-arg-nested-braces.stderr @@ -0,0 +1,11 @@ +error: generic parameters may not be used in const operations + --> $DIR/trivial-const-arg-nested-braces.rs:4:35 + | +LL | fn foo() -> A<{ { N } }> { + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + +error: aborting due to 1 previous error +