diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 71777e0117ab8..e920a736e5077 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -141,14 +141,15 @@ impl Path { self.segments.first().is_some_and(|segment| segment.ident.name == kw::PathRoot) } - /// Check if this path is potentially a trivial const arg, i.e., one that can _potentially_ - /// be represented without an anon const in the HIR. - /// - /// Returns true iff the path has exactly one segment, and it has no generic args - /// (i.e., it is _potentially_ a const parameter). - #[tracing::instrument(level = "debug", ret)] - pub fn is_potential_trivial_const_arg(&self) -> bool { - self.segments.len() == 1 && self.segments.iter().all(|seg| seg.args.is_none()) + /// Checks if this path is just a simple one-word `PATH` - i.e. the inverse of + /// [`Path::from_ident`] + pub fn is_single_argless_ident(&self) -> bool { + self.segments.len() == 1 && self.segments[0].args.is_none() + } + + /// The inverse of [`Path::from_ident`] - if this path is just a simple one-word `PATH` + pub fn as_single_argless_ident(&self) -> Option { + self.is_single_argless_ident().then(|| self.segments[0].ident) } } @@ -1407,7 +1408,7 @@ impl Expr { /// be represented without an anon const in the HIR. /// /// This will unwrap at most one block level (curly braces). After that, if the expression - /// is a path, it mostly dispatches to [`Path::is_potential_trivial_const_arg`]. + /// is a path, it mostly dispatches to [`Path::is_single_argless_ident`]. /// /// This function will only allow paths with no qself, before dispatching to the `Path` /// function of the same name. @@ -1417,7 +1418,7 @@ impl Expr { pub fn is_potential_trivial_const_arg(&self) -> bool { let this = self.maybe_unwrap_block(); if let ExprKind::Path(None, path) = &this.kind - && path.is_potential_trivial_const_arg() + && path.is_single_argless_ident() { true } else { diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 7784fc17828aa..e78df3079f4b9 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1442,25 +1442,22 @@ impl<'hir> LoweringContext<'_, 'hir> { // type and value namespaces. If we resolved the path in the value namespace, we // transform it into a generic const argument. // + // Note that even under `#![feature(min_generic_const_args)]`, only plain paths + // to constants are allowed - e.g. `A::` and + // `A::>` are disallowed (they must be wrapped in `{ }`). + // // FIXME: Should we be handling `(PATH_TO_CONST)`? - TyKind::Path(None, path) => { - if let Some(res) = self - .get_partial_res(ty.id) - .and_then(|partial_res| partial_res.full_res()) - { - if !res.matches_ns(Namespace::TypeNS) - && path.is_potential_trivial_const_arg() - { - debug!( - "lower_generic_arg: Lowering type argument as const argument: {:?}", - ty, - ); - - let ct = - self.lower_const_path_to_const_arg(path, res, ty.id, ty.span); - return GenericArg::Const(ct.try_as_ambig_ct().unwrap()); - } - } + TyKind::Path(None, path) + if path.is_single_argless_ident() + && let Some(res) = self + .get_partial_res(ty.id) + .and_then(|partial_res| partial_res.full_res()) + && !res.matches_ns(Namespace::TypeNS) => + { + let ct = + self.lower_const_path_to_const_arg(&None, path, res, ty.id, ty.span); + let ct = self.arena.alloc(ct); + return GenericArg::Const(ct.try_as_ambig_ct().unwrap()); } TyKind::DirectConstArg(expr) if self.tcx.features().min_generic_const_args() => @@ -2591,6 +2588,10 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_array_length_to_const_arg(&mut self, c: &AnonConst) -> &'hir hir::ConstArg<'hir> { // We cannot just match on `ExprKind::Underscore` as `(_)` is represented as // `ExprKind::Paren(ExprKind::Underscore)` and should also be lowered to `GenericArg::Infer` + // + // FIXME(macroless_generic_const_args): Handling of underscores should be moved into + // lower_expr_to_const_arg_direct. It is left here as retaining compatibility of what is + // currently allowed on stable gets hairy and annoying otherwise. match c.value.peel_parens().kind { ExprKind::Underscore => { let ct_kind = hir::ConstArgKind::Infer(()); @@ -2610,27 +2611,16 @@ impl<'hir> LoweringContext<'_, 'hir> { #[instrument(level = "debug", skip(self))] fn lower_const_path_to_const_arg( &mut self, + qself: &Option>, path: &Path, res: Res, - ty_id: NodeId, + id: NodeId, span: Span, - ) -> &'hir hir::ConstArg<'hir> { - let tcx = self.tcx; - - let is_trivial_path = path.is_potential_trivial_const_arg() - && matches!(res, Res::Def(DefKind::ConstParam, _)); - let ct_kind = if is_trivial_path || tcx.features().macroless_generic_const_args() { - let qpath = self.lower_qpath( - ty_id, - &None, - path, - ParamMode::Explicit, - AllowReturnTypeNotation::No, - // FIXME(mgca): update for `fn foo() -> Bar>` support - ImplTraitContext::Disallowed(ImplTraitPosition::Path), - None, - ); - hir::ConstArgKind::Path(qpath) + ) -> hir::ConstArg<'hir> { + let context = self.ambient_direct_const_arg_context(); + if self.can_lower_path_to_const_arg_direct(qself, path, span, Some(res), context).is_ok() { + let span = self.lower_span(span); + self.lower_path_to_const_arg_direct(id, None, qself, path, span) } else { // Construct an AnonConst where the expr is the "ty"'s path. let node_id = self.next_node_id(); @@ -2644,8 +2634,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let hir_id = self.lower_node_id(node_id); let path_expr = Expr { - id: ty_id, - kind: ExprKind::Path(None, path.clone()), + id, + kind: ExprKind::Path(qself.clone(), path.clone()), span, attrs: AttrVec::new(), tokens: None, @@ -2659,14 +2649,12 @@ impl<'hir> LoweringContext<'_, 'hir> { span, }) }); - hir::ConstArgKind::Anon(ct) - }; - - self.arena.alloc(hir::ConstArg { - hir_id: self.next_id(), - kind: ct_kind, - span: self.lower_span(span), - }) + hir::ConstArg { + hir_id: self.next_id(), + kind: hir::ConstArgKind::Anon(ct), + span: self.lower_span(span), + } + } } fn lower_const_item_rhs( @@ -2703,9 +2691,39 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + fn ambient_direct_const_arg_context(&self) -> DirectConstArgContext { + if self.tcx.features().macroless_generic_const_args() { + DirectConstArgContext::MacrolessMinGenericConstArgs + } else if self.tcx.features().min_generic_const_args() { + DirectConstArgContext::MinGenericConstArgs + } else { + DirectConstArgContext::Stable + } + } + + fn can_lower_path_to_const_arg_direct( + &self, + qself: &Option>, + path: &Path, + span: Span, + res: Option>, + context: DirectConstArgContext, + ) -> Result<(), UnrepresentableConstArgError> { + if let DirectConstArgContext::MacrolessMinGenericConstArgs = context { + Ok(()) + } else if qself.is_none() + && path.is_single_argless_ident() + && matches!(res, Some(Res::Def(DefKind::ConstParam, _))) + { + Ok(()) + } else { + Err(UnrepresentableConstArgError { span, will_create_def_ids: false }) + } + } + #[instrument(level = "debug", skip(self), ret)] fn can_lower_expr_to_const_arg_direct( - &mut self, + &self, expr: &Expr, context: DirectConstArgContext, ) -> Result<(), UnrepresentableConstArgError> { @@ -2727,19 +2745,10 @@ impl<'hir> LoweringContext<'_, 'hir> { } Ok(()) } - (ExprKind::Path(_, _), MacrolessMinGenericConstArgs) => Ok(()), - (ExprKind::Path(_, path), _) => { - if path.is_potential_trivial_const_arg() - && matches!( - self.get_partial_res(expr.id) - .and_then(|partial_res| partial_res.full_res()), - Some(Res::Def(DefKind::ConstParam, _)) - ) - { - Ok(()) - } else { - Err(UnrepresentableConstArgError::new(expr)) - } + (ExprKind::Path(qself, path), _) => { + let res = + self.get_partial_res(expr.id).and_then(|partial_res| partial_res.full_res()); + self.can_lower_path_to_const_arg_direct(qself, path, expr.span, res, context) } (ExprKind::Struct(se), MacrolessMinGenericConstArgs) => { for f in &se.fields { @@ -2754,6 +2763,9 @@ impl<'hir> LoweringContext<'_, 'hir> { Ok(()) } (ExprKind::Underscore, MacrolessMinGenericConstArgs) => Ok(()), + (ExprKind::Paren(expr), MacrolessMinGenericConstArgs) => { + self.can_lower_expr_to_const_arg_direct(expr, context) + } (ExprKind::Block(block, _), MacrolessMinGenericConstArgs) if let [stmt] = block.stmts.as_slice() && let StmtKind::Expr(expr) = &stmt.kind => @@ -2776,6 +2788,31 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + /// It is not allowed to call this function without checking can_lower_path_to_const_arg_direct + /// first, as we assume all feature gates/etc. have been checked already. + fn lower_path_to_const_arg_direct( + &mut self, + id: NodeId, + id_override: Option, + qself: &Option>, + path: &Path, + span: Span, + ) -> hir::ConstArg<'hir> { + let qpath = self.lower_qpath( + id, + qself, + path, + ParamMode::Explicit, + AllowReturnTypeNotation::No, + // FIXME(mgca): update for `fn foo() -> Bar>` support + ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, + ); + + let node_id = id_override.unwrap_or(id); + ConstArg { hir_id: self.lower_node_id(node_id), kind: hir::ConstArgKind::Path(qpath), span } + } + /// It is not allowed to call this function without checking can_lower_expr_to_const_arg_direct /// first, as we assume all feature gates/etc. have been checked already. #[instrument(level = "debug", skip(self), ret)] @@ -2822,22 +2859,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } ExprKind::Path(qself, path) => { - let qpath = self.lower_qpath( - expr.id, - qself, - path, - ParamMode::Explicit, - AllowReturnTypeNotation::No, - // FIXME(mgca): update for `fn foo() -> Bar>` support - ImplTraitContext::Disallowed(ImplTraitPosition::Path), - None, - ); - - ConstArg { - hir_id: self.lower_node_id(node_id), - kind: hir::ConstArgKind::Path(qpath), - span, - } + self.lower_path_to_const_arg_direct(expr.id, id_override, qself, path, span) } ExprKind::Struct(se) => { let path = self.lower_qpath( @@ -2896,11 +2918,12 @@ impl<'hir> LoweringContext<'_, 'hir> { kind: hir::ConstArgKind::Infer(()), span, }, + ExprKind::Paren(expr) => self.lower_expr_to_const_arg_direct(expr, id_override), ExprKind::Block(block, _) if let [stmt] = block.stmts.as_slice() && let StmtKind::Expr(expr) = &stmt.kind => { - return self.lower_expr_to_const_arg_direct(expr, id_override); + self.lower_expr_to_const_arg_direct(expr, id_override) } ExprKind::Lit(literal) => { let span = self.lower_span(expr.span); @@ -2984,14 +3007,7 @@ impl<'hir> LoweringContext<'_, 'hir> { anon.value.maybe_unwrap_block() }; - let context = if self.tcx.features().macroless_generic_const_args() { - DirectConstArgContext::MacrolessMinGenericConstArgs - } else if self.tcx.features().min_generic_const_args() { - DirectConstArgContext::MinGenericConstArgs - } else { - DirectConstArgContext::Stable - }; - + let context = self.ambient_direct_const_arg_context(); if self.can_lower_expr_to_const_arg_direct(expr, context).is_ok() { return self.lower_expr_to_const_arg_direct(expr, Some(anon.id)); } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 535d11d00d718..06bd3ae2a234e 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -1040,6 +1040,11 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc self.visit_ty(element_ty); self.resolve_anon_const(length, AnonConstKind::ConstArg(IsRepeatExpr::No)); } + TyKind::DirectConstArg(expr) => self.resolve_anon_const_manual( + true, + AnonConstKind::ConstArg(IsRepeatExpr::No), + |this| this.resolve_expr(expr, None), + ), _ => visit::walk_ty(self, ty), } self.diag_metadata.current_trait_object = prev; @@ -1301,8 +1306,8 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc } } + #[instrument(level = "debug", skip(self))] fn visit_generic_arg(&mut self, arg: &'ast GenericArg) { - debug!("visit_generic_arg({:?})", arg); let prev = replace(&mut self.diag_metadata.currently_processing_generic_args, true); match arg { GenericArg::Type(ty) => { @@ -1311,31 +1316,25 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc // namespace first, and if that fails we try again in the value namespace. If // resolution in the value namespace succeeds, we have an generic const argument on // our hands. + // + // We cannot disambiguate multi-segment paths right now as that requires type + // checking. if let TyKind::Path(None, ref path) = ty.kind - // We cannot disambiguate multi-segment paths right now as that requires type - // checking. - && path.is_potential_trivial_const_arg() + && let Some(ident) = path.as_single_argless_ident() + && self.maybe_resolve_ident_in_lexical_scope(ident, TypeNS).is_none() + && self.maybe_resolve_ident_in_lexical_scope(ident, ValueNS).is_some() { - let mut check_ns = |ns| { - self.maybe_resolve_ident_in_lexical_scope(path.segments[0].ident, ns) - .is_some() - }; - if !check_ns(TypeNS) && check_ns(ValueNS) { - self.resolve_anon_const_manual( - true, - AnonConstKind::ConstArg(IsRepeatExpr::No), - |this| { - this.smart_resolve_path(ty.id, &None, path, PathSource::Expr(None)); - this.visit_path(path); - }, - ); - - self.diag_metadata.currently_processing_generic_args = prev; - return; - } + self.resolve_anon_const_manual( + true, + AnonConstKind::ConstArg(IsRepeatExpr::No), + |this| { + this.smart_resolve_path(ty.id, &None, path, PathSource::Expr(None)); + this.visit_path(path); + }, + ) + } else { + self.visit_ty(ty) } - - self.visit_ty(ty); } GenericArg::Lifetime(lt) => self.visit_lifetime(lt, visit::LifetimeCtxt::GenericArg), GenericArg::Const(ct) => { diff --git a/tests/ui/const-generics/mgca/direct-const-arg-correct-rib.rs b/tests/ui/const-generics/mgca/direct-const-arg-correct-rib.rs new file mode 100644 index 0000000000000..9f088a05b98f9 --- /dev/null +++ b/tests/ui/const-generics/mgca/direct-const-arg-correct-rib.rs @@ -0,0 +1,13 @@ +//! make sure TyKind::DirectConstArg resolves properly with the correct ribs and doesn't ICE +#![feature(min_generic_const_args)] + +struct S; +struct V; +impl S { + fn f(self) { + let _: V; + //~^ ERROR attempt to use a non-constant value in a constant + } +} + +fn main() {} diff --git a/tests/ui/const-generics/mgca/direct-const-arg-correct-rib.stderr b/tests/ui/const-generics/mgca/direct-const-arg-correct-rib.stderr new file mode 100644 index 0000000000000..76f189e38e4d5 --- /dev/null +++ b/tests/ui/const-generics/mgca/direct-const-arg-correct-rib.stderr @@ -0,0 +1,8 @@ +error: attempt to use a non-constant value in a constant + --> $DIR/direct-const-arg-correct-rib.rs:8:42 + | +LL | let _: V; + | ^^^^ help: try using `Self` + +error: aborting due to 1 previous error + diff --git a/tests/ui/const-generics/mgca/direct_const_arg-infer-as-type.rs b/tests/ui/const-generics/mgca/direct_const_arg-infer-as-type.rs new file mode 100644 index 0000000000000..c3b1bef810592 --- /dev/null +++ b/tests/ui/const-generics/mgca/direct_const_arg-infer-as-type.rs @@ -0,0 +1,12 @@ +//@ check-pass +//! It is very weird and mostly a compiler implementation quirk that direct_const_arg!(_) is allowed +//! to infer to a type rather than forcing it to be a constant. This test simply tracks/asserts the +//! current behavior. +#![feature(min_generic_const_args)] + +struct S(T); + +fn main() { + let _: S = S(2u32); + let _: S<{ core::direct_const_arg!(_) }> = S(2u32); +} diff --git a/tests/ui/const-generics/mgca/paren.rs b/tests/ui/const-generics/mgca/paren.rs new file mode 100644 index 0000000000000..3f4dd491da949 --- /dev/null +++ b/tests/ui/const-generics/mgca/paren.rs @@ -0,0 +1,36 @@ +//@ check-pass +//! See also: tests/ui/const-generics/paren.rs +#![feature(min_generic_const_args, generic_const_items)] + +struct Thing; + +type const A: usize = N; + +fn f() { + let _: [u32; core::direct_const_arg!(_)] = [5; 5]; + let _: [u32; core::direct_const_arg!((_))] = [5; 5]; + let _: [u32; core::direct_const_arg!({ _ })] = [5; 5]; + let _: [u32; core::direct_const_arg!({ (_) })] = [5; 5]; + let _: [u32; core::direct_const_arg!(N)] = [5; _]; + let _: [u32; core::direct_const_arg!((N))] = [5; _]; + let _: [u32; core::direct_const_arg!({ N })] = [5; _]; + let _: [u32; core::direct_const_arg!({ (N) })] = [5; _]; + let _: [u32; core::direct_const_arg!(A::)] = [5; _]; + let _: [u32; core::direct_const_arg!((A::))] = [5; _]; + let _: [u32; core::direct_const_arg!({ A:: })] = [5; _]; + let _: [u32; core::direct_const_arg!({ (A::) })] = [5; _]; + let _: Thing = Thing::<5>; + let _: Thing = Thing::<5>; + let _: Thing = Thing::<5>; + let _: Thing = Thing::<5>; + let _: Thing = Thing; + let _: Thing = Thing; + let _: Thing = Thing; + let _: Thing = Thing; + let _: Thing)> = Thing; + let _: Thing))> = Thing; + let _: Thing })> = Thing; + let _: Thing) })> = Thing; +} + +fn main() {} diff --git a/tests/ui/const-generics/paren.rs b/tests/ui/const-generics/paren.rs new file mode 100644 index 0000000000000..cf07ff8c035d7 --- /dev/null +++ b/tests/ui/const-generics/paren.rs @@ -0,0 +1,28 @@ +//! What exactly is allowed on stable is a bit strange and arbitrary. This tests various +//! combinations of parens and braces to make sure they remain stable. + +struct Thing; + +fn f() { + let _: [u32; _] = [5; 5]; + let _: [u32; (_)] = [5; 5]; + let _: [u32; { _ }] = [5; 5]; //~ ERROR in expressions, `_` can only be used on the left-hand side of an assignment + let _: [u32; { (_) }] = [5; 5]; //~ ERROR in expressions, `_` can only be used on the left-hand side of an assignment + let _: [u32; N] = [5; _]; + let _: [u32; (N)] = [5; _]; //~ ERROR generic parameters may not be used in const operations + let _: [u32; { N }] = [5; _]; + let _: [u32; { (N) }] = [5; _]; //~ ERROR generic parameters may not be used in const operations + let _: [u32; { { N } }] = [5; _]; //~ ERROR generic parameters may not be used in const operations + let _: Thing<_> = Thing::<5>; + let _: Thing<(_)> = Thing::<5>; + let _: Thing<{ _ }> = Thing::<5>; //~ ERROR in expressions, `_` can only be used on the left-hand side of an assignment + let _: Thing<{ (_) }> = Thing::<5>; //~ ERROR in expressions, `_` can only be used on the left-hand side of an assignment + let _: Thing = Thing; + let _: Thing<(N)> = Thing; //~ ERROR cannot find type `N` in this scope + //~| ERROR unresolved item provided when a constant was expected + let _: Thing<{ N }> = Thing; + let _: Thing<{ (N) }> = Thing; //~ ERROR generic parameters may not be used in const operations + let _: Thing<{ { N } }> = Thing; //~ ERROR generic parameters may not be used in const operations +} + +fn main() {} diff --git a/tests/ui/const-generics/paren.stderr b/tests/ui/const-generics/paren.stderr new file mode 100644 index 0000000000000..fafcf126b903a --- /dev/null +++ b/tests/ui/const-generics/paren.stderr @@ -0,0 +1,97 @@ +error: generic parameters may not be used in const operations + --> $DIR/paren.rs:12:19 + | +LL | let _: [u32; (N)] = [5; _]; + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments here, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + = help: alternatively, you can use `#![feature(generic_const_args)]` and extract the expression into a `type const` item + +error: generic parameters may not be used in const operations + --> $DIR/paren.rs:14:21 + | +LL | let _: [u32; { (N) }] = [5; _]; + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments here, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + = help: alternatively, you can use `#![feature(generic_const_args)]` and extract the expression into a `type const` item + +error: generic parameters may not be used in const operations + --> $DIR/paren.rs:15:22 + | +LL | let _: [u32; { { N } }] = [5; _]; + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments here, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + = help: alternatively, you can use `#![feature(generic_const_args)]` and extract the expression into a `type const` item + +error: generic parameters may not be used in const operations + --> $DIR/paren.rs:24:21 + | +LL | let _: Thing<{ (N) }> = Thing; + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments here, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + = help: alternatively, you can use `#![feature(generic_const_args)]` and extract the expression into a `type const` item + +error: generic parameters may not be used in const operations + --> $DIR/paren.rs:25:22 + | +LL | let _: Thing<{ { N } }> = Thing; + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments here, i.e. `N` + = help: add `#![feature(generic_const_exprs)]` to allow generic const expressions + = help: alternatively, you can use `#![feature(generic_const_args)]` and extract the expression into a `type const` item + +error[E0573]: cannot find type `N` in this scope + --> $DIR/paren.rs:21:19 + | +LL | let _: Thing<(N)> = Thing; + | ^ not found in this scope + | + = note: a const parameter named `N` exists in another namespace + +error: in expressions, `_` can only be used on the left-hand side of an assignment + --> $DIR/paren.rs:9:20 + | +LL | let _: [u32; { _ }] = [5; 5]; + | ^ `_` not allowed here + +error: in expressions, `_` can only be used on the left-hand side of an assignment + --> $DIR/paren.rs:10:21 + | +LL | let _: [u32; { (_) }] = [5; 5]; + | ^ `_` not allowed here + +error: in expressions, `_` can only be used on the left-hand side of an assignment + --> $DIR/paren.rs:18:20 + | +LL | let _: Thing<{ _ }> = Thing::<5>; + | ^ `_` not allowed here + +error: in expressions, `_` can only be used on the left-hand side of an assignment + --> $DIR/paren.rs:19:21 + | +LL | let _: Thing<{ (_) }> = Thing::<5>; + | ^ `_` not allowed here + +error[E0747]: unresolved item provided when a constant was expected + --> $DIR/paren.rs:21:19 + | +LL | let _: Thing<(N)> = Thing; + | ^ + | +help: if this generic argument was intended as a const parameter, surround it with braces + | +LL | let _: Thing<({ N })> = Thing; + | + + + +error: aborting due to 11 previous errors + +Some errors have detailed explanations: E0573, E0747. +For more information about an error, try `rustc --explain E0573`.