Skip to content

Commit 3538bc1

Browse files
authored
Rollup merge of #143619 - beetrees:varargs-named, r=jdonszelmann
`c_variadic`: Add future-incompatibility warning for `...` arguments without a pattern outside of `extern` blocks This PR makes `...` arguments without a pattern in non-foreign functions (such as the argument in `unsafe extern "C" fn f(...) {}`) a future-compatibility warning; making this error would be consistent with how `unsafe extern "C" fn f(u32) {}` is handled. Allowing `...` arguments without a pattern in non-foreign functions is a source of confusion for programmers coming from C, where the `...` parameter is never named and instead calling `va_start` is required; disallowing `...` arguments without a pattern also improves the overall consistency of the Rust language by matching the treatment of other arguments without patterns. `...` arguments without a pattern in `extern` blocks (such as `unsafe extern "C" { fn f(...); }`) continue to compile without warnings after this PR, as they are already stable and heavily used (and don't cause the mentioned confusion as they are just being used in function declarations). As all the syntax gating for `c_variadic` has been done post-expansion, this is technically a breaking change. In particular, code like this has compiled on stable since Rust 1.35.0: ```rust #[cfg(any())] // Equivalent to the more recent #[cfg(false)] unsafe extern "C" fn bar(_: u32, ...) {} ``` Since this is more or less a stability hole and a Crater run shows only the `binrw` crate is using this, I think it would be ok to break this. This will require a lang FCP. The idea of rejecting `...` pre-expansion was first raised here #143546 (comment). Tracking issue: #44930 cc `@folkertdev` `@workingjubilee` r? `@joshtriplett`
2 parents 5b9211c + 02e1f44 commit 3538bc1

31 files changed

+496
-192
lines changed

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ declare_lint_pass! {
144144
UNUSED_UNSAFE,
145145
UNUSED_VARIABLES,
146146
USELESS_DEPRECATED,
147+
VARARGS_WITHOUT_PATTERN,
147148
WARNINGS,
148149
// tidy-alphabetical-end
149150
]
@@ -5295,3 +5296,50 @@ declare_lint! {
52955296
report_in_deps: false,
52965297
};
52975298
}
5299+
5300+
declare_lint! {
5301+
/// The `varargs_without_pattern` lint detects when `...` is used as an argument to a
5302+
/// non-foreign function without any pattern being specified.
5303+
///
5304+
/// ### Example
5305+
///
5306+
/// ```rust
5307+
/// // Using `...` in non-foreign function definitions is unstable, however stability is
5308+
/// // currently only checked after attributes are expanded, so using `#[cfg(false)]` here will
5309+
/// // allow this to compile on stable Rust.
5310+
/// #[cfg(false)]
5311+
/// fn foo(...) {
5312+
///
5313+
/// }
5314+
/// ```
5315+
///
5316+
/// {{produces}}
5317+
///
5318+
/// ### Explanation
5319+
///
5320+
/// Patterns are currently required for all non-`...` arguments in function definitions (with
5321+
/// some exceptions in the 2015 edition). Requiring `...` arguments to have patterns in
5322+
/// non-foreign function definitions makes the language more consistent, and removes a source of
5323+
/// confusion for the unstable C variadic feature. `...` arguments without a pattern are already
5324+
/// stable and widely used in foreign function definitions; this lint only affects non-foreign
5325+
/// function definitions.
5326+
///
5327+
/// Using `...` (C varargs) in a non-foreign function definition is currently unstable. However,
5328+
/// stability checking for the `...` syntax in non-foreign function definitions is currently
5329+
/// implemented after attributes have been expanded, meaning that if the attribute removes the
5330+
/// use of the unstable syntax (e.g. `#[cfg(false)]`, or a procedural macro), the code will
5331+
/// compile on stable Rust; this is the only situation where this lint affects code that
5332+
/// compiles on stable Rust.
5333+
///
5334+
/// This is a [future-incompatible] lint to transition this to a hard error in the future.
5335+
///
5336+
/// [future-incompatible]: ../index.md#future-incompatible-lints
5337+
pub VARARGS_WITHOUT_PATTERN,
5338+
Warn,
5339+
"detects usage of `...` arguments without a pattern in non-foreign items",
5340+
@future_incompatible = FutureIncompatibleInfo {
5341+
reason: FutureIncompatibilityReason::FutureReleaseError,
5342+
reference: "issue #145544 <https://github.com/rust-lang/rust/issues/145544>",
5343+
report_in_deps: false,
5344+
};
5345+
}

compiler/rustc_parse/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,9 @@ parse_use_if_else = use an `if-else` expression instead
10041004
parse_use_let_not_auto = write `let` instead of `auto` to introduce a new variable
10051005
parse_use_let_not_var = write `let` instead of `var` to introduce a new variable
10061006
1007+
parse_varargs_without_pattern = missing pattern for `...` argument
1008+
.suggestion = name the argument, or use `_` to continue ignoring it
1009+
10071010
parse_visibility_not_followed_by_item = visibility `{$vis}` is not followed by an item
10081011
.label = the visibility
10091012
.help = you likely meant to define an item, e.g., `{$vis} fn foo() {"{}"}`

compiler/rustc_parse/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3645,3 +3645,10 @@ impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub {
36453645
}
36463646
}
36473647
}
3648+
3649+
#[derive(LintDiagnostic)]
3650+
#[diag(parse_varargs_without_pattern)]
3651+
pub(crate) struct VarargsWithoutPattern {
3652+
#[suggestion(code = "_: ...", applicability = "machine-applicable")]
3653+
pub span: Span,
3654+
}

compiler/rustc_parse/src/parser/attr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ impl<'a> Parser<'a> {
201201
AttrWrapper::empty(),
202202
true,
203203
false,
204-
FnParseMode { req_name: |_| true, context: FnContext::Free, req_body: true },
204+
FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true },
205205
ForceCollect::No,
206206
) {
207207
Ok(Some(item)) => {

compiler/rustc_parse/src/parser/diagnostics.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use crate::errors::{
4545
};
4646
use crate::parser::FnContext;
4747
use crate::parser::attr::InnerAttrPolicy;
48+
use crate::parser::item::IsDotDotDot;
4849
use crate::{exp, fluent_generated as fluent};
4950

5051
/// Creates a placeholder argument.
@@ -2284,7 +2285,7 @@ impl<'a> Parser<'a> {
22842285
let maybe_emit_anon_params_note = |this: &mut Self, err: &mut Diag<'_>| {
22852286
let ed = this.token.span.with_neighbor(this.prev_token.span).edition();
22862287
if matches!(fn_parse_mode.context, crate::parser::item::FnContext::Trait)
2287-
&& (fn_parse_mode.req_name)(ed)
2288+
&& (fn_parse_mode.req_name)(ed, IsDotDotDot::No)
22882289
{
22892290
err.note("anonymous parameters are removed in the 2018 edition (see RFC 1685)");
22902291
}

compiler/rustc_parse/src/parser/item.rs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_ast::{
1212
use rustc_ast_pretty::pprust;
1313
use rustc_errors::codes::*;
1414
use rustc_errors::{Applicability, PResult, StashKey, struct_span_code_err};
15+
use rustc_session::lint::builtin::VARARGS_WITHOUT_PATTERN;
1516
use rustc_span::edit_distance::edit_distance;
1617
use rustc_span::edition::Edition;
1718
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span, Symbol, kw, source_map, sym};
@@ -119,7 +120,7 @@ impl<'a> Parser<'a> {
119120
impl<'a> Parser<'a> {
120121
pub fn parse_item(&mut self, force_collect: ForceCollect) -> PResult<'a, Option<Box<Item>>> {
121122
let fn_parse_mode =
122-
FnParseMode { req_name: |_| true, context: FnContext::Free, req_body: true };
123+
FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true };
123124
self.parse_item_(fn_parse_mode, force_collect).map(|i| i.map(Box::new))
124125
}
125126

@@ -976,7 +977,7 @@ impl<'a> Parser<'a> {
976977
force_collect: ForceCollect,
977978
) -> PResult<'a, Option<Option<Box<AssocItem>>>> {
978979
let fn_parse_mode =
979-
FnParseMode { req_name: |_| true, context: FnContext::Impl, req_body: true };
980+
FnParseMode { req_name: |_, _| true, context: FnContext::Impl, req_body: true };
980981
self.parse_assoc_item(fn_parse_mode, force_collect)
981982
}
982983

@@ -985,7 +986,7 @@ impl<'a> Parser<'a> {
985986
force_collect: ForceCollect,
986987
) -> PResult<'a, Option<Option<Box<AssocItem>>>> {
987988
let fn_parse_mode = FnParseMode {
988-
req_name: |edition| edition >= Edition::Edition2018,
989+
req_name: |edition, _| edition >= Edition::Edition2018,
989990
context: FnContext::Trait,
990991
req_body: false,
991992
};
@@ -1245,8 +1246,11 @@ impl<'a> Parser<'a> {
12451246
&mut self,
12461247
force_collect: ForceCollect,
12471248
) -> PResult<'a, Option<Option<Box<ForeignItem>>>> {
1248-
let fn_parse_mode =
1249-
FnParseMode { req_name: |_| true, context: FnContext::Free, req_body: false };
1249+
let fn_parse_mode = FnParseMode {
1250+
req_name: |_, is_dot_dot_dot| is_dot_dot_dot == IsDotDotDot::No,
1251+
context: FnContext::Free,
1252+
req_body: false,
1253+
};
12501254
Ok(self.parse_item_(fn_parse_mode, force_collect)?.map(
12511255
|Item { attrs, id, span, vis, kind, tokens }| {
12521256
let kind = match ForeignItemKind::try_from(kind) {
@@ -2133,7 +2137,7 @@ impl<'a> Parser<'a> {
21332137
Visibility { span: DUMMY_SP, kind: VisibilityKind::Inherited, tokens: None };
21342138
// We use `parse_fn` to get a span for the function
21352139
let fn_parse_mode =
2136-
FnParseMode { req_name: |_| true, context: FnContext::Free, req_body: true };
2140+
FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true };
21372141
match self.parse_fn(
21382142
&mut AttrVec::new(),
21392143
fn_parse_mode,
@@ -2366,8 +2370,16 @@ impl<'a> Parser<'a> {
23662370
/// The function decides if, per-parameter `p`, `p` must have a pattern or just a type.
23672371
///
23682372
/// This function pointer accepts an edition, because in edition 2015, trait declarations
2369-
/// were allowed to omit parameter names. In 2018, they became required.
2370-
type ReqName = fn(Edition) -> bool;
2373+
/// were allowed to omit parameter names. In 2018, they became required. It also accepts an
2374+
/// `IsDotDotDot` parameter, as `extern` function declarations and function pointer types are
2375+
/// allowed to omit the name of the `...` but regular function items are not.
2376+
type ReqName = fn(Edition, IsDotDotDot) -> bool;
2377+
2378+
#[derive(Copy, Clone, PartialEq)]
2379+
pub(crate) enum IsDotDotDot {
2380+
Yes,
2381+
No,
2382+
}
23712383

23722384
/// Parsing configuration for functions.
23732385
///
@@ -2400,6 +2412,9 @@ pub(crate) struct FnParseMode {
24002412
/// to true.
24012413
/// * The span is from Edition 2015. In particular, you can get a
24022414
/// 2015 span inside a 2021 crate using macros.
2415+
///
2416+
/// Or if `IsDotDotDot::Yes`, this function will also return `false` if the item being parsed
2417+
/// is inside an `extern` block.
24032418
pub(super) req_name: ReqName,
24042419
/// The context in which this function is parsed, used for diagnostics.
24052420
/// This indicates the fn is a free function or method and so on.
@@ -3049,11 +3064,25 @@ impl<'a> Parser<'a> {
30493064
return Ok((res?, Trailing::No, UsePreAttrPos::No));
30503065
}
30513066

3052-
let is_name_required = match this.token.kind {
3053-
token::DotDotDot => false,
3054-
_ => (fn_parse_mode.req_name)(
3055-
this.token.span.with_neighbor(this.prev_token.span).edition(),
3056-
),
3067+
let is_dot_dot_dot = if this.token.kind == token::DotDotDot {
3068+
IsDotDotDot::Yes
3069+
} else {
3070+
IsDotDotDot::No
3071+
};
3072+
let is_name_required = (fn_parse_mode.req_name)(
3073+
this.token.span.with_neighbor(this.prev_token.span).edition(),
3074+
is_dot_dot_dot,
3075+
);
3076+
let is_name_required = if is_name_required && is_dot_dot_dot == IsDotDotDot::Yes {
3077+
this.psess.buffer_lint(
3078+
VARARGS_WITHOUT_PATTERN,
3079+
this.token.span,
3080+
ast::CRATE_NODE_ID,
3081+
errors::VarargsWithoutPattern { span: this.token.span },
3082+
);
3083+
false
3084+
} else {
3085+
is_name_required
30573086
};
30583087
let (pat, ty) = if is_name_required || this.is_named_param() {
30593088
debug!("parse_param_general parse_pat (is_name_required:{})", is_name_required);

compiler/rustc_parse/src/parser/path.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ impl<'a> Parser<'a> {
404404
// Inside parenthesized type arguments, we want types only, not names.
405405
let mode = FnParseMode {
406406
context: FnContext::Free,
407-
req_name: |_| false,
407+
req_name: |_, _| false,
408408
req_body: false,
409409
};
410410
let param = p.parse_param_general(&mode, false, false);

compiler/rustc_parse/src/parser/stmt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ impl<'a> Parser<'a> {
154154
attrs.clone(), // FIXME: unwanted clone of attrs
155155
false,
156156
true,
157-
FnParseMode { req_name: |_| true, context: FnContext::Free, req_body: true },
157+
FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true },
158158
force_collect,
159159
)? {
160160
self.mk_stmt(lo.to(item.span), StmtKind::Item(Box::new(item)))

compiler/rustc_parse/src/parser/ty.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ impl<'a> Parser<'a> {
824824
self.recover_fn_ptr_with_generics(lo, &mut params, param_insertion_point)?;
825825
}
826826
let mode = crate::parser::item::FnParseMode {
827-
req_name: |_| false,
827+
req_name: |_, _| false,
828828
context: FnContext::Free,
829829
req_body: false,
830830
};
@@ -1380,7 +1380,8 @@ impl<'a> Parser<'a> {
13801380
self.bump();
13811381
let args_lo = self.token.span;
13821382
let snapshot = self.create_snapshot_for_diagnostic();
1383-
let mode = FnParseMode { req_name: |_| false, context: FnContext::Free, req_body: false };
1383+
let mode =
1384+
FnParseMode { req_name: |_, _| false, context: FnContext::Free, req_body: false };
13841385
match self.parse_fn_decl(&mode, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) {
13851386
Ok(decl) => {
13861387
self.dcx().emit_err(ExpectedFnPathFoundFnKeyword { fn_token_span });
@@ -1471,7 +1472,8 @@ impl<'a> Parser<'a> {
14711472

14721473
// Parse `(T, U) -> R`.
14731474
let inputs_lo = self.token.span;
1474-
let mode = FnParseMode { req_name: |_| false, context: FnContext::Free, req_body: false };
1475+
let mode =
1476+
FnParseMode { req_name: |_, _| false, context: FnContext::Free, req_body: false };
14751477
let inputs: ThinVec<_> =
14761478
self.parse_fn_params(&mode)?.into_iter().map(|input| input.ty).collect();
14771479
let inputs_span = inputs_lo.to(self.prev_token.span);

tests/pretty/hir-fn-variadic.pp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616

1717
fn main() {
1818
fn g1(_: extern "C" fn(_: u8, va: ...)) { }
19-
fn g2(_: extern "C" fn(_: u8, ...)) { }
19+
fn g2(_: extern "C" fn(_: u8, _: ...)) { }
2020
fn g3(_: extern "C" fn(u8, va: ...)) { }
21-
fn g4(_: extern "C" fn(u8, ...)) { }
21+
fn g4(_: extern "C" fn(u8, _: ...)) { }
2222

2323
fn g5(_: extern "C" fn(va: ...)) { }
24-
fn g6(_: extern "C" fn(...)) { }
24+
fn g6(_: extern "C" fn(_: ...)) { }
2525

2626
{
2727
let _ =

0 commit comments

Comments
 (0)