diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 6d44feec2c496..82b41e13cccff 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -9,7 +9,9 @@ use rustc_data_structures::thin_vec::ThinVec; use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::def::Res; +use rustc_hir::definitions::DefPathData; use rustc_session::parse::feature_err; +use rustc_span::hygiene::ExpnId; use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned}; use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::{hygiene::ForLoopLoc, DUMMY_SP}; @@ -42,8 +44,12 @@ impl<'hir> LoweringContext<'_, 'hir> { } ExprKind::Tup(ref elts) => hir::ExprKind::Tup(self.lower_exprs(elts)), ExprKind::Call(ref f, ref args) => { - let f = self.lower_expr(f); - hir::ExprKind::Call(f, self.lower_exprs(args)) + if let Some(legacy_args) = self.resolver.legacy_const_generic_args(f) { + self.lower_legacy_const_generics((**f).clone(), args.clone(), &legacy_args) + } else { + let f = self.lower_expr(f); + hir::ExprKind::Call(f, self.lower_exprs(args)) + } } ExprKind::MethodCall(ref seg, ref args, span) => { let hir_seg = self.arena.alloc(self.lower_path_segment( @@ -292,6 +298,54 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + fn lower_legacy_const_generics( + &mut self, + mut f: Expr, + args: Vec>, + legacy_args_idx: &[usize], + ) -> hir::ExprKind<'hir> { + let path = match f.kind { + ExprKind::Path(None, ref mut path) => path, + _ => unreachable!(), + }; + + // Split the arguments into const generics and normal arguments + let mut real_args = vec![]; + let mut generic_args = vec![]; + for (idx, arg) in args.into_iter().enumerate() { + if legacy_args_idx.contains(&idx) { + let parent_def_id = self.current_hir_id_owner.last().unwrap().0; + let node_id = self.resolver.next_node_id(); + + // Add a definition for the in-band const def. + self.resolver.create_def( + parent_def_id, + node_id, + DefPathData::AnonConst, + ExpnId::root(), + arg.span, + ); + + let anon_const = AnonConst { id: node_id, value: arg }; + generic_args.push(AngleBracketedArg::Arg(GenericArg::Const(anon_const))); + } else { + real_args.push(arg); + } + } + + // Add generic args to the last element of the path. + let last_segment = path.segments.last_mut().unwrap(); + assert!(last_segment.args.is_none()); + last_segment.args = Some(AstP(GenericArgs::AngleBracketed(AngleBracketedArgs { + span: DUMMY_SP, + args: generic_args, + }))); + + // Now lower everything as normal. + let f = self.lower_expr(&f); + hir::ExprKind::Call(f, self.lower_exprs(&real_args)) + } + /// Emit an error and lower `ast::ExprKind::Let(pat, scrutinee)` into: /// ```rust /// match scrutinee { pats => true, _ => false } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 05b417effd491..c564ada0395e9 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -175,6 +175,8 @@ pub trait ResolverAstLowering { fn item_generics_num_lifetimes(&self, def: DefId, sess: &Session) -> usize; + fn legacy_const_generic_args(&mut self, expr: &Expr) -> Option>; + /// Obtains resolution for a `NodeId` with a single resolution. fn get_partial_res(&mut self, id: NodeId) -> Option; diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index ac50703b5444e..072062dd615d8 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -470,6 +470,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!(rustc_promotable, AssumedUsed, template!(Word), IMPL_DETAIL), rustc_attr!(rustc_args_required_const, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE), + rustc_attr!(rustc_legacy_const_generics, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE), // ========================================================================== // Internal attributes, Layout related: diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 828c025d38d0b..0f860d11dc21a 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -468,6 +468,10 @@ impl CStore { pub fn num_def_ids(&self, cnum: CrateNum) -> usize { self.get_crate_data(cnum).num_def_ids() } + + pub fn item_attrs(&self, def_id: DefId, sess: &Session) -> Vec { + self.get_crate_data(def_id.krate).get_item_attrs(def_id.index, sess).collect() + } } impl CrateStore for CStore { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index bf9b7e588bd4b..cf08881cd473d 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -91,6 +91,8 @@ impl CheckAttrVisitor<'tcx> { self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target) } else if self.tcx.sess.check_name(attr, sym::naked) { self.check_naked(hir_id, attr, span, target) + } else if self.tcx.sess.check_name(attr, sym::rustc_legacy_const_generics) { + self.check_rustc_legacy_const_generics(&attr, span, target, item) } else { // lint-only checks if self.tcx.sess.check_name(attr, sym::cold) { @@ -750,6 +752,105 @@ impl CheckAttrVisitor<'tcx> { } } + /// Checks if `#[rustc_legacy_const_generics]` is applied to a function and has a valid argument. + fn check_rustc_legacy_const_generics( + &self, + attr: &Attribute, + span: &Span, + target: Target, + item: Option>, + ) -> bool { + let is_function = matches!(target, Target::Fn | Target::Method(..)); + if !is_function { + self.tcx + .sess + .struct_span_err(attr.span, "attribute should be applied to a function") + .span_label(*span, "not a function") + .emit(); + return false; + } + + let list = match attr.meta_item_list() { + // The attribute form is validated on AST. + None => return false, + Some(it) => it, + }; + + let (decl, generics) = match item { + Some(ItemLike::Item(Item { + kind: ItemKind::Fn(FnSig { decl, .. }, generics, _), + .. + })) => (decl, generics), + _ => bug!("should be a function item"), + }; + + for param in generics.params { + match param.kind { + hir::GenericParamKind::Const { .. } => {} + _ => { + self.tcx + .sess + .struct_span_err( + attr.span, + "#[rustc_legacy_const_generics] functions must \ + only have const generics", + ) + .span_label(param.span, "non-const generic parameter") + .emit(); + return false; + } + } + } + + if list.len() != generics.params.len() { + self.tcx + .sess + .struct_span_err( + attr.span, + "#[rustc_legacy_const_generics] must have one index for each generic parameter", + ) + .span_label(generics.span, "generic parameters") + .emit(); + return false; + } + + let arg_count = decl.inputs.len() as u128 + generics.params.len() as u128; + let mut invalid_args = vec![]; + for meta in list { + if let Some(LitKind::Int(val, _)) = meta.literal().map(|lit| &lit.kind) { + if *val >= arg_count { + let span = meta.span(); + self.tcx + .sess + .struct_span_err(span, "index exceeds number of arguments") + .span_label( + span, + format!( + "there {} only {} argument{}", + if arg_count != 1 { "are" } else { "is" }, + arg_count, + pluralize!(arg_count) + ), + ) + .emit(); + return false; + } + } else { + invalid_args.push(meta.span()); + } + } + + if !invalid_args.is_empty() { + self.tcx + .sess + .struct_span_err(invalid_args, "arguments should be non-negative integers") + .emit(); + false + } else { + true + } + } + /// Checks if `#[link_section]` is applied to a function or static. fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) { match target { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 701d48a982d36..6f24d7e941351 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -2326,8 +2326,22 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { ExprKind::Call(ref callee, ref arguments) => { self.resolve_expr(callee, Some(expr)); - for argument in arguments { - self.resolve_expr(argument, None); + let const_args = self.r.legacy_const_generic_args(callee).unwrap_or(Vec::new()); + for (idx, argument) in arguments.iter().enumerate() { + // Constant arguments need to be treated as AnonConst since + // that is how they will be later lowered to HIR. + if const_args.contains(&idx) { + self.with_constant_rib( + IsRepeatExpr::No, + argument.is_potential_trivial_const_param(), + None, + |this| { + this.resolve_expr(argument, None); + }, + ); + } else { + self.resolve_expr(argument, None); + } } } ExprKind::Type(ref type_expr, ref ty) => { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 77fbbaa1532af..e63fd4ce6357d 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -29,6 +29,7 @@ use rustc_ast::unwrap_or; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{self as ast, NodeId}; use rustc_ast::{Crate, CRATE_NODE_ID}; +use rustc_ast::{Expr, ExprKind, LitKind}; use rustc_ast::{ItemKind, ModKind, Path}; use rustc_ast_lowering::ResolverAstLowering; use rustc_ast_pretty::pprust; @@ -995,6 +996,8 @@ pub struct Resolver<'a> { /// Some way to know that we are in a *trait* impl in `visit_assoc_item`. /// FIXME: Replace with a more general AST map (together with some other fields). trait_impl_items: FxHashSet, + + legacy_const_generic_args: FxHashMap>>, } /// Nothing really interesting here; it just provides memory for the rest of the crate. @@ -1076,6 +1079,10 @@ impl ResolverAstLowering for Resolver<'_> { self.cstore().item_generics_num_lifetimes(def_id, sess) } + fn legacy_const_generic_args(&mut self, expr: &Expr) -> Option> { + self.legacy_const_generic_args(expr) + } + fn get_partial_res(&mut self, id: NodeId) -> Option { self.partial_res_map.get(&id).cloned() } @@ -1316,6 +1323,7 @@ impl<'a> Resolver<'a> { invocation_parents, next_disambiguator: Default::default(), trait_impl_items: Default::default(), + legacy_const_generic_args: Default::default(), }; let root_parent_scope = ParentScope::module(graph_root, &resolver); @@ -3308,6 +3316,61 @@ impl<'a> Resolver<'a> { pub fn opt_span(&self, def_id: DefId) -> Option { if let Some(def_id) = def_id.as_local() { Some(self.def_id_to_span[def_id]) } else { None } } + + /// Checks if an expression refers to a function marked with + /// `#[rustc_legacy_const_generics]` and returns the argument index list + /// from the attribute. + pub fn legacy_const_generic_args(&mut self, expr: &Expr) -> Option> { + if let ExprKind::Path(None, path) = &expr.kind { + // Don't perform legacy const generics rewriting if the path already + // has generic arguments. + if path.segments.last().unwrap().args.is_some() { + return None; + } + + let partial_res = self.partial_res_map.get(&expr.id)?; + if partial_res.unresolved_segments() != 0 { + return None; + } + + if let Res::Def(def::DefKind::Fn, def_id) = partial_res.base_res() { + // We only support cross-crate argument rewriting. Uses + // within the same crate should be updated to use the new + // const generics style. + if def_id.is_local() { + return None; + } + + if let Some(v) = self.legacy_const_generic_args.get(&def_id) { + return v.clone(); + } + + let parse_attrs = || { + let attrs = self.cstore().item_attrs(def_id, self.session); + let attr = attrs + .iter() + .find(|a| self.session.check_name(a, sym::rustc_legacy_const_generics))?; + let mut ret = vec![]; + for meta in attr.meta_item_list()? { + match meta.literal()?.kind { + LitKind::Int(a, _) => { + ret.push(a as usize); + } + _ => panic!("invalid arg index"), + } + } + Some(ret) + }; + + // Cache the lookup to avoid parsing attributes for an iterm + // multiple times. + let ret = parse_attrs(); + self.legacy_const_generic_args.insert(def_id, ret.clone()); + return ret; + } + } + None + } } fn names_to_string(names: &[Symbol]) -> String { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b112402ffe371..6fe65a8a1cda1 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -981,6 +981,7 @@ symbols! { rustc_layout, rustc_layout_scalar_valid_range_end, rustc_layout_scalar_valid_range_start, + rustc_legacy_const_generics, rustc_macro_transparency, rustc_mir, rustc_nonnull_optimization_guaranteed, diff --git a/src/test/ui/auxiliary/legacy-const-generics.rs b/src/test/ui/auxiliary/legacy-const-generics.rs new file mode 100644 index 0000000000000..67352a2fbbb1c --- /dev/null +++ b/src/test/ui/auxiliary/legacy-const-generics.rs @@ -0,0 +1,6 @@ +#![feature(rustc_attrs)] + +#[rustc_legacy_const_generics(1)] +pub fn foo(x: usize, z: usize) -> [usize; 3] { + [x, Y, z] +} diff --git a/src/test/ui/invalid-rustc_args_required_const-arguments.rs b/src/test/ui/invalid/invalid-rustc_args_required_const-arguments.rs similarity index 100% rename from src/test/ui/invalid-rustc_args_required_const-arguments.rs rename to src/test/ui/invalid/invalid-rustc_args_required_const-arguments.rs diff --git a/src/test/ui/invalid-rustc_args_required_const-arguments.stderr b/src/test/ui/invalid/invalid-rustc_args_required_const-arguments.stderr similarity index 100% rename from src/test/ui/invalid-rustc_args_required_const-arguments.stderr rename to src/test/ui/invalid/invalid-rustc_args_required_const-arguments.stderr diff --git a/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.rs b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.rs new file mode 100644 index 0000000000000..3d8478f06db0e --- /dev/null +++ b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.rs @@ -0,0 +1,38 @@ +#![feature(rustc_attrs)] + +#[rustc_legacy_const_generics(0)] //~ ERROR #[rustc_legacy_const_generics] must have one index for +fn foo1() {} + +#[rustc_legacy_const_generics(1)] //~ ERROR index exceeds number of arguments +fn foo2() {} + +#[rustc_legacy_const_generics(2)] //~ ERROR index exceeds number of arguments +fn foo3(_: u8) {} + +#[rustc_legacy_const_generics(a)] //~ ERROR arguments should be non-negative integers +fn foo4() {} + +#[rustc_legacy_const_generics(1, a, 2, b)] //~ ERROR arguments should be non-negative integers +fn foo5() {} + +#[rustc_legacy_const_generics(0)] //~ ERROR attribute should be applied to a function +struct S; + +#[rustc_legacy_const_generics(0usize)] //~ ERROR suffixed literals are not allowed in attributes +fn foo6() {} + +extern { + #[rustc_legacy_const_generics(1)] //~ ERROR attribute should be applied to a function + fn foo7(); //~ ERROR foreign items may not have const parameters +} + +#[rustc_legacy_const_generics(0)] //~ ERROR #[rustc_legacy_const_generics] functions must only have +fn foo8() {} + +#[rustc_legacy_const_generics] //~ ERROR malformed `rustc_legacy_const_generics` attribute +fn bar1() {} + +#[rustc_legacy_const_generics = 1] //~ ERROR malformed `rustc_legacy_const_generics` attribute +fn bar2() {} + +fn main() {} diff --git a/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.stderr b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.stderr new file mode 100644 index 0000000000000..1f55a8e72d2cb --- /dev/null +++ b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.stderr @@ -0,0 +1,87 @@ +error: suffixed literals are not allowed in attributes + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:21:31 + | +LL | #[rustc_legacy_const_generics(0usize)] + | ^^^^^^ + | + = help: instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.) + +error: malformed `rustc_legacy_const_generics` attribute input + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:32:1 + | +LL | #[rustc_legacy_const_generics] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_legacy_const_generics(N)]` + +error: malformed `rustc_legacy_const_generics` attribute input + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:35:1 + | +LL | #[rustc_legacy_const_generics = 1] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_legacy_const_generics(N)]` + +error: #[rustc_legacy_const_generics] must have one index for each generic parameter + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:3:1 + | +LL | #[rustc_legacy_const_generics(0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn foo1() {} + | - generic parameters + +error: index exceeds number of arguments + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:6:31 + | +LL | #[rustc_legacy_const_generics(1)] + | ^ there is only 1 argument + +error: index exceeds number of arguments + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:9:31 + | +LL | #[rustc_legacy_const_generics(2)] + | ^ there are only 2 arguments + +error: arguments should be non-negative integers + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:12:31 + | +LL | #[rustc_legacy_const_generics(a)] + | ^ + +error: arguments should be non-negative integers + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:15:34 + | +LL | #[rustc_legacy_const_generics(1, a, 2, b)] + | ^ ^ + +error: attribute should be applied to a function + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:18:1 + | +LL | #[rustc_legacy_const_generics(0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | struct S; + | --------- not a function + +error: #[rustc_legacy_const_generics] functions must only have const generics + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:29:1 + | +LL | #[rustc_legacy_const_generics(0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn foo8() {} + | - non-const generic parameter + +error: attribute should be applied to a function + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:25:5 + | +LL | #[rustc_legacy_const_generics(1)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn foo7(); + | -------------------------- not a function + +error[E0044]: foreign items may not have const parameters + --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:26:5 + | +LL | fn foo7(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ can't have const parameters + | + = help: replace the const parameters with concrete consts + +error: aborting due to 12 previous errors + +For more information about this error, try `rustc --explain E0044`. diff --git a/src/test/ui/legacy-const-generics-bad.rs b/src/test/ui/legacy-const-generics-bad.rs new file mode 100644 index 0000000000000..538eee337cc6d --- /dev/null +++ b/src/test/ui/legacy-const-generics-bad.rs @@ -0,0 +1,16 @@ +// aux-build:legacy-const-generics.rs + +extern crate legacy_const_generics; + +fn foo() { + let a = 1; + legacy_const_generics::foo(0, a, 2); + //~^ ERROR attempt to use a non-constant value in a constant + + legacy_const_generics::foo(0, N, 2); + + legacy_const_generics::foo(0, N + 1, 2); + //~^ ERROR generic parameters may not be used in const operations +} + +fn main() {} diff --git a/src/test/ui/legacy-const-generics-bad.stderr b/src/test/ui/legacy-const-generics-bad.stderr new file mode 100644 index 0000000000000..5a44b8e706553 --- /dev/null +++ b/src/test/ui/legacy-const-generics-bad.stderr @@ -0,0 +1,20 @@ +error[E0435]: attempt to use a non-constant value in a constant + --> $DIR/legacy-const-generics-bad.rs:7:35 + | +LL | let a = 1; + | ----- help: consider using `const` instead of `let`: `const a` +LL | legacy_const_generics::foo(0, a, 2); + | ^ non-constant value + +error: generic parameters may not be used in const operations + --> $DIR/legacy-const-generics-bad.rs:12:35 + | +LL | legacy_const_generics::foo(0, N + 1, 2); + | ^ cannot perform const operation using `N` + | + = help: const parameters may only be used as standalone arguments, i.e. `N` + = help: use `#![feature(const_generics)]` and `#![feature(const_evaluatable_checked)]` to allow generic const expressions + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0435`. diff --git a/src/test/ui/legacy-const-generics.rs b/src/test/ui/legacy-const-generics.rs new file mode 100644 index 0000000000000..9abc72d98e6ce --- /dev/null +++ b/src/test/ui/legacy-const-generics.rs @@ -0,0 +1,18 @@ +// aux-build:legacy-const-generics.rs +// run-pass + +#![feature(rustc_attrs)] + +extern crate legacy_const_generics; + +#[rustc_legacy_const_generics(1)] +pub fn bar(x: usize, z: usize) -> [usize; 3] { + [x, Y, z] +} + +fn main() { + assert_eq!(legacy_const_generics::foo(0 + 0, 1 + 1, 2 + 2), [0, 2, 4]); + assert_eq!(legacy_const_generics::foo::<{1 + 1}>(0 + 0, 2 + 2), [0, 2, 4]); + // FIXME: Only works cross-crate + //assert_eq!(bar(0, 1, 2), [0, 1, 2]); +}