Skip to content

Commit 3a2583b

Browse files
committed
Auto merge of #155852 - teor2345:fn-arg-splat-fndecl-bit, r=<try>
[perf experiment]: Use 1 bit to track splatting in FnDecl
2 parents ca9a134 + dd91585 commit 3a2583b

86 files changed

Lines changed: 2097 additions & 182 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ no_llvm_build
5656
/src/bootstrap/target
5757
/src/ci/citool/target
5858
/src/tools/x/target
59+
# Created by `x rust-analyzer[-clippy]` tests
60+
/library/core/src/iter/traits/target
61+
/library/core/src/slice/target
5962
# Created by `x vendor`
6063
/vendor
6164
# Created by default with `src/ci/docker/run.sh`

compiler/rustc_ast/src/ast.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3055,9 +3055,40 @@ impl FnDecl {
30553055
pub fn has_self(&self) -> bool {
30563056
self.inputs.get(0).is_some_and(Param::is_self)
30573057
}
3058+
30583059
pub fn c_variadic(&self) -> bool {
30593060
self.inputs.last().is_some_and(|arg| matches!(arg.ty.kind, TyKind::CVarArgs))
30603061
}
3062+
3063+
/// The marker index for "no splatted arguments".
3064+
/// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX`.
3065+
// FIXME(splat): if we remove this limit from hir::FnDecl and FnSig, all instances of this
3066+
// constant can go away entirely.
3067+
pub const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX;
3068+
3069+
/// Returns a splatted argument index and its span, if any splatted arguments are present.
3070+
#[inline]
3071+
pub fn splatted(&self) -> Option<(u16 /* arg_index */, Span)> {
3072+
let (index, span) = self.inputs.iter().enumerate().find_map(|(index, arg)| {
3073+
arg.attrs.iter().find_map(|attr| {
3074+
attr.has_name(sym::splat).then_some((u16::try_from(index).unwrap(), attr.span))
3075+
})
3076+
})?;
3077+
3078+
if index == Self::NO_SPLATTED_ARG_INDEX {
3079+
// AST validation has already checked the splatted argument index is valid, so just
3080+
// ignore invalid indexes here.
3081+
None
3082+
} else {
3083+
Some((index, span))
3084+
}
3085+
}
3086+
3087+
/// Returns `true` if the function has a splatted argument.
3088+
#[inline(always)]
3089+
pub fn has_splatted_arg(&self) -> bool {
3090+
self.splatted().is_some()
3091+
}
30613092
}
30623093

30633094
/// Is the trait definition an auto trait?

compiler/rustc_ast_lowering/src/delegation.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
141141

142142
let is_method = self.is_method(sig_id, span);
143143

144-
let (param_count, c_variadic) = self.param_count(sig_id);
144+
let (param_count, c_variadic, has_splatted_arg) = self.param_count(sig_id);
145145

146146
let mut generics =
147147
self.uplift_delegation_generics(delegation, sig_id, item_id, is_method);
@@ -154,8 +154,14 @@ impl<'hir> LoweringContext<'_, 'hir> {
154154
span,
155155
);
156156

157-
let decl =
158-
self.lower_delegation_decl(sig_id, param_count, c_variadic, span, &generics);
157+
let decl = self.lower_delegation_decl(
158+
sig_id,
159+
param_count,
160+
c_variadic,
161+
has_splatted_arg,
162+
span,
163+
&generics,
164+
);
159165

160166
let sig = self.lower_delegation_sig(sig_id, decl, span);
161167
let ident = self.lower_ident(delegation.ident);
@@ -269,17 +275,25 @@ impl<'hir> LoweringContext<'_, 'hir> {
269275
self.get_partial_res(node_id).and_then(|r| r.expect_full_res().opt_def_id())
270276
}
271277

272-
// Function parameter count, including C variadic `...` if present.
273-
fn param_count(&self, def_id: DefId) -> (usize, bool /*c_variadic*/) {
278+
// Function parameter count, including C variadic `...` and `#[splat]` if present.
279+
fn param_count(
280+
&self,
281+
def_id: DefId,
282+
) -> (usize, bool /*c_variadic*/, bool /*has_splatted_arg*/) {
274283
let sig = self.tcx.fn_sig(def_id).skip_binder().skip_binder();
275-
(sig.inputs().len() + usize::from(sig.c_variadic()), sig.c_variadic())
284+
(
285+
sig.inputs().len() + usize::from(sig.c_variadic()),
286+
sig.c_variadic(),
287+
sig.splatted().is_some(),
288+
)
276289
}
277290

278291
fn lower_delegation_decl(
279292
&mut self,
280293
sig_id: DefId,
281294
param_count: usize,
282295
c_variadic: bool,
296+
has_splatted_arg: bool,
283297
span: Span,
284298
generics: &GenericsGenerationResults<'hir>,
285299
) -> &'hir hir::FnDecl<'hir> {
@@ -314,7 +328,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
314328
output: hir::FnRetTy::Return(output),
315329
fn_decl_kind: FnDeclFlags::default()
316330
.set_lifetime_elision_allowed(true)
317-
.set_c_variadic(c_variadic),
331+
.set_c_variadic(c_variadic)
332+
.set_has_splatted_arg(has_splatted_arg),
318333
})
319334
}
320335

compiler/rustc_ast_lowering/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1767,12 +1767,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
17671767
coro: Option<CoroutineKind>,
17681768
) -> &'hir hir::FnDecl<'hir> {
17691769
let c_variadic = decl.c_variadic();
1770+
let mut splatted_arg_index = decl.splatted();
17701771

17711772
// Skip the `...` (`CVarArgs`) trailing arguments from the AST,
17721773
// as they are not explicit in HIR/Ty function signatures.
17731774
// (instead, the `c_variadic` flag is set to `true`)
17741775
let mut inputs = &decl.inputs[..];
17751776
if decl.c_variadic() {
1777+
// Splat + variadic errors in AST validation, so just ignore one of them here.
1778+
splatted_arg_index = None;
17761779
inputs = &inputs[..inputs.len() - 1];
17771780
}
17781781
let inputs = self.arena.alloc_from_iter(inputs.iter().map(|param| {
@@ -1860,7 +1863,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
18601863
}
18611864
}))
18621865
.set_lifetime_elision_allowed(self.resolver.lifetime_elision_allowed(fn_node_id))
1863-
.set_c_variadic(c_variadic);
1866+
.set_c_variadic(c_variadic)
1867+
.set_has_splatted_arg(splatted_arg_index.is_some());
1868+
1869+
if let Some((index, span)) = splatted_arg_index {
1870+
// For performance, just lower the one attribute fn args care about to HIR.
1871+
let local_id = inputs[usize::from(index)].hir_id.local_id;
1872+
assert!(!self.attrs.contains_key(&local_id));
1873+
self.attrs.insert(
1874+
local_id,
1875+
arena_vec![self; hir::Attribute::Parsed(hir::attrs::AttributeKind::Splat(span))],
1876+
);
1877+
}
18641878

18651879
self.arena.alloc(hir::FnDecl { inputs, output, fn_decl_kind })
18661880
}

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,8 @@ impl<'a> AstValidator<'a> {
350350

351351
fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) {
352352
self.check_decl_num_args(fn_decl);
353-
self.check_decl_cvariadic_pos(fn_decl);
353+
let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl);
354+
self.check_decl_splatting(fn_decl, c_variadic_span);
354355
self.check_decl_attrs(fn_decl);
355356
self.check_decl_self_param(fn_decl, self_semantic);
356357
}
@@ -368,17 +369,68 @@ impl<'a> AstValidator<'a> {
368369
/// Emits an error if a function declaration has a variadic parameter in the
369370
/// beginning or middle of parameter list.
370371
/// Example: `fn foo(..., x: i32)` will emit an error.
371-
fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) {
372+
/// Returns true if a C-variadic parameter is found.
373+
fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) -> Option<Span> {
374+
let mut c_variadic_span = None;
375+
372376
match &*fn_decl.inputs {
373377
[ps @ .., _] => {
374378
for Param { ty, span, .. } in ps {
375379
if let TyKind::CVarArgs = ty.kind {
380+
c_variadic_span = Some(*span);
376381
self.dcx().emit_err(errors::FnParamCVarArgsNotLast { span: *span });
377382
}
378383
}
379384
}
380385
_ => {}
381386
}
387+
388+
if let Some(Param { ty, span, .. }) = &fn_decl.inputs.last() {
389+
if let TyKind::CVarArgs = ty.kind {
390+
c_variadic_span = Some(*span);
391+
}
392+
}
393+
394+
c_variadic_span
395+
}
396+
397+
/// Emits an error if a function declaration has more than one splatted argument, with a
398+
/// C-variadic parameter, or a splat at an unsupported index (for performance).
399+
/// Example: `fn foo(#[splat] x: (), #[splat] y: ())` will emit an error.
400+
fn check_decl_splatting(&self, fn_decl: &FnDecl, c_variadic_span: Option<Span>) {
401+
let (splatted_arg_indexes, mut splatted_spans): (Vec<u16>, Vec<Span>) = fn_decl
402+
.inputs
403+
.iter()
404+
.enumerate()
405+
.filter_map(|(index, arg)| {
406+
arg.attrs
407+
.iter()
408+
.any(|attr| attr.has_name(sym::splat))
409+
.then_some((u16::try_from(index).unwrap(), arg.span))
410+
})
411+
.unzip();
412+
413+
// A splatted argument at the "no splatted" marker index is not supported (this is an
414+
// unlikely edge case).
415+
if let (Some(&splatted_arg_index), Some(&splatted_span)) =
416+
(splatted_arg_indexes.last(), splatted_spans.last())
417+
&& splatted_arg_index == FnDecl::NO_SPLATTED_ARG_INDEX
418+
{
419+
self.dcx()
420+
.emit_err(errors::InvalidSplattedArg { splatted_arg_index, span: splatted_span });
421+
}
422+
423+
// Multiple splatted arguments are invalid: we can't know which arguments go in each splat.
424+
if splatted_arg_indexes.len() > 1 {
425+
self.dcx().emit_err(errors::DuplicateSplattedArgs { spans: splatted_spans.clone() });
426+
}
427+
428+
if let Some(c_variadic_span) = c_variadic_span
429+
&& !splatted_spans.is_empty()
430+
{
431+
splatted_spans.push(c_variadic_span);
432+
self.dcx().emit_err(errors::CVarArgsAndSplat { spans: splatted_spans });
433+
}
382434
}
383435

384436
fn check_decl_attrs(&self, fn_decl: &FnDecl) {
@@ -394,6 +446,7 @@ impl<'a> AstValidator<'a> {
394446
sym::deny,
395447
sym::expect,
396448
sym::forbid,
449+
sym::splat,
397450
sym::warn,
398451
];
399452
!attr.has_any_name(&arr) && rustc_attr_parsing::is_builtin_attr(*attr)

compiler/rustc_ast_passes/src/errors.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,33 @@ pub(crate) struct FnParamCVarArgsNotLast {
124124
pub span: Span,
125125
}
126126

127+
#[derive(Diagnostic)]
128+
#[diag("`#[splat]` is not supported on argument index {$splatted_arg_index}")]
129+
#[help("remove `#[splat]`, or use it on an argument closer to the start of the argument list")]
130+
pub(crate) struct InvalidSplattedArg {
131+
pub splatted_arg_index: u16,
132+
133+
#[primary_span]
134+
#[label("`#[splat]` is not supported here")]
135+
pub span: Span,
136+
}
137+
138+
#[derive(Diagnostic)]
139+
#[diag("multiple `#[splat]`s are not allowed in the same function")]
140+
#[help("remove `#[splat]` from all but one argument")]
141+
pub(crate) struct DuplicateSplattedArgs {
142+
#[primary_span]
143+
pub spans: Vec<Span>,
144+
}
145+
146+
#[derive(Diagnostic)]
147+
#[diag("`...` and `#[splat]` are not allowed in the same function")]
148+
#[help("remove `#[splat]` or remove `...`")]
149+
pub(crate) struct CVarArgsAndSplat {
150+
#[primary_span]
151+
pub spans: Vec<Span>,
152+
}
153+
127154
#[derive(Diagnostic)]
128155
#[diag("documentation comments cannot be applied to function parameters")]
129156
pub(crate) struct FnParamDocComment {
@@ -132,6 +159,7 @@ pub(crate) struct FnParamDocComment {
132159
pub span: Span,
133160
}
134161

162+
// FIXME(splat): add splat to the allowed built-in attributes when it is complete/stabilized
135163
#[derive(Diagnostic)]
136164
#[diag(
137165
"allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters"

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
506506
gate_all!(pin_ergonomics, "pinned reference syntax is experimental");
507507
gate_all!(postfix_match, "postfix match is experimental");
508508
gate_all!(return_type_notation, "return type notation is experimental");
509+
gate_all!(splat, "`fn(#[splat] (a, ...))` is incomplete", "call as func((a, ...)) instead");
509510
gate_all!(super_let, "`super let` is experimental");
510511
gate_all!(try_blocks_heterogeneous, "`try bikeshed` expression is experimental");
511512
gate_all!(unsafe_binders, "unsafe binder types are experimental");

compiler/rustc_attr_parsing/src/attributes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ pub(crate) mod rustc_allocator;
6464
pub(crate) mod rustc_dump;
6565
pub(crate) mod rustc_internal;
6666
pub(crate) mod semantics;
67+
pub(crate) mod splat;
6768
pub(crate) mod stability;
6869
pub(crate) mod test_attrs;
6970
pub(crate) mod traits;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//! Attribute parsing for the `#[splat]` function argument overloading attribute.
2+
//! This attribute modifies typecheck to support overload resolution, then modifies codegen for performance.
3+
4+
use super::prelude::*;
5+
6+
pub(crate) struct SplatParser;
7+
8+
impl<S: Stage> NoArgsAttributeParser<S> for SplatParser {
9+
const PATH: &[Symbol] = &[sym::splat];
10+
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
11+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
12+
Allow(Target::Param),
13+
// FIXME(splat): only allow MacroCall if the macro creates an argument
14+
Allow(Target::MacroCall),
15+
]);
16+
const CREATE: fn(Span) -> AttributeKind = AttributeKind::Splat;
17+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ use crate::attributes::rustc_allocator::*;
5656
use crate::attributes::rustc_dump::*;
5757
use crate::attributes::rustc_internal::*;
5858
use crate::attributes::semantics::*;
59+
use crate::attributes::splat::*;
5960
use crate::attributes::stability::*;
6061
use crate::attributes::test_attrs::*;
6162
use crate::attributes::traits::*;
@@ -338,6 +339,7 @@ attribute_parsers!(
338339
Single<WithoutArgs<RustcStrictCoherenceParser>>,
339340
Single<WithoutArgs<RustcTrivialFieldReadsParser>>,
340341
Single<WithoutArgs<RustcUnsafeSpecializationMarkerParser>>,
342+
Single<WithoutArgs<SplatParser>>,
341343
Single<WithoutArgs<ThreadLocalParser>>,
342344
Single<WithoutArgs<TrackCallerParser>>,
343345
// tidy-alphabetical-end

0 commit comments

Comments
 (0)