Skip to content

Commit e8e12c0

Browse files
committed
c_variadic: Add future-incompatibility warning for ... arguments without a pattern outside of extern blocks
1 parent adcb3d3 commit e8e12c0

27 files changed

+401
-166
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,5 +1051,8 @@ lint_useless_ptr_null_checks_ref = references are not nullable, so checking them
10511051
10521052
lint_uses_power_alignment = repr(C) does not follow the power alignment rule. This may affect platform C ABI compatibility for this type
10531053
1054+
lint_varargs_without_pattern = missing pattern for `...` argument
1055+
.suggestion = name the argument, or use `_` to continue ignoring it
1056+
10541057
lint_variant_size_differences =
10551058
enum variant is more than three times larger ({$largest} bytes) than the next largest

compiler/rustc_lint/src/early/diagnostics.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,5 +468,8 @@ pub fn decorate_builtin_lint(
468468
BuiltinLintDiag::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by } => {
469469
lints::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by }.decorate_lint(diag)
470470
}
471+
BuiltinLintDiag::VarargsWithoutPattern { span } => {
472+
lints::VarargsWithoutPattern { span }.decorate_lint(diag)
473+
}
471474
}
472475
}

compiler/rustc_lint/src/lints.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3215,6 +3215,13 @@ pub(crate) struct ReservedMultihash {
32153215
pub suggestion: Span,
32163216
}
32173217

3218+
#[derive(LintDiagnostic)]
3219+
#[diag(lint_varargs_without_pattern)]
3220+
pub(crate) struct VarargsWithoutPattern {
3221+
#[suggestion(code = "_: ...", applicability = "machine-applicable")]
3222+
pub span: Span,
3223+
}
3224+
32183225
#[derive(Debug)]
32193226
pub(crate) struct MismatchedLifetimeSyntaxes {
32203227
pub inputs: LifetimeSyntaxCategories<Vec<Span>>,

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ declare_lint_pass! {
141141
UNUSED_UNSAFE,
142142
UNUSED_VARIABLES,
143143
USELESS_DEPRECATED,
144+
VARARGS_WITHOUT_PATTERN,
144145
WARNINGS,
145146
// tidy-alphabetical-end
146147
]
@@ -5099,3 +5100,50 @@ declare_lint! {
50995100
report_in_deps: true,
51005101
};
51015102
}
5103+
5104+
declare_lint! {
5105+
/// The `varargs_without_pattern` lint detects when `...` is used as an argument to a
5106+
/// non-foreign function without any pattern being specified.
5107+
///
5108+
/// ### Example
5109+
///
5110+
/// ```rust
5111+
/// // Using `...` in non-foreign function definitions is unstable, however stability is
5112+
/// // currently only checked after attributes are expanded, so using `#[cfg(false)]` here will
5113+
/// // allow this to compile on stable Rust.
5114+
/// #[cfg(false)]
5115+
/// fn foo(...) {
5116+
///
5117+
/// }
5118+
/// ```
5119+
///
5120+
/// {{produces}}
5121+
///
5122+
/// ### Explanation
5123+
///
5124+
/// Patterns are currently required for all non-`...` arguments in function definitions (with
5125+
/// some exceptions in the 2015 edition). Requiring `...` arguments to have patterns in
5126+
/// non-foreign function defitions makes the language more consistent, and removes a source of
5127+
/// confusion for the unstable C variadic feature. `...` arguments without a pattern are already
5128+
/// stable and widely used in foreign function definitions; this lint only affects non-foreign
5129+
/// function defitions.
5130+
///
5131+
/// Using `...` (C varargs) in a non-foreign function definition is currently unstable. However,
5132+
/// stability checking for the `...` syntax in non-foreign function definitions is currently
5133+
/// implemented after attributes have been expanded, meaning that if the attribute removes the
5134+
/// use of the unstable syntax (e.g. `#[cfg(false)]`, or a procedural macro), the code will
5135+
/// compile on stable Rust; this is the only situtation where this lint affects code that
5136+
/// compiles on stable Rust.
5137+
///
5138+
/// This is a [future-incompatible] lint to transition this to a hard error in the future.
5139+
///
5140+
/// [future-incompatible]: ../index.md#future-incompatible-lints
5141+
pub VARARGS_WITHOUT_PATTERN,
5142+
Warn,
5143+
"detects usage of `...` arguments without a pattern in non-foreign items",
5144+
@future_incompatible = FutureIncompatibleInfo {
5145+
reason: FutureIncompatibilityReason::FutureReleaseError,
5146+
reference: "issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>",
5147+
report_in_deps: false,
5148+
};
5149+
}

compiler/rustc_lint_defs/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,9 @@ pub enum BuiltinLintDiag {
807807
cfg_name: Symbol,
808808
controlled_by: &'static str,
809809
},
810+
VarargsWithoutPattern {
811+
span: Span,
812+
},
810813
}
811814

812815
/// Lints that are buffered up early on in the `Session` before the

compiler/rustc_parse/src/parser/attr.rs

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

compiler/rustc_parse/src/parser/item.rs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use rustc_ast::{self as ast};
1111
use rustc_ast_pretty::pprust;
1212
use rustc_errors::codes::*;
1313
use rustc_errors::{Applicability, PResult, StashKey, struct_span_code_err};
14+
use rustc_session::lint::BuiltinLintDiag;
15+
use rustc_session::lint::builtin::VARARGS_WITHOUT_PATTERN;
1416
use rustc_span::edit_distance::edit_distance;
1517
use rustc_span::edition::Edition;
1618
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span, Symbol, kw, source_map, sym};
@@ -117,7 +119,7 @@ impl<'a> Parser<'a> {
117119

118120
impl<'a> Parser<'a> {
119121
pub fn parse_item(&mut self, force_collect: ForceCollect) -> PResult<'a, Option<P<Item>>> {
120-
let fn_parse_mode = FnParseMode { req_name: |_| true, req_body: true };
122+
let fn_parse_mode = FnParseMode { req_name: |_, _| true, req_body: true };
121123
self.parse_item_(fn_parse_mode, force_collect).map(|i| i.map(P))
122124
}
123125

@@ -949,7 +951,7 @@ impl<'a> Parser<'a> {
949951
&mut self,
950952
force_collect: ForceCollect,
951953
) -> PResult<'a, Option<Option<P<AssocItem>>>> {
952-
let fn_parse_mode = FnParseMode { req_name: |_| true, req_body: true };
954+
let fn_parse_mode = FnParseMode { req_name: |_, _| true, req_body: true };
953955
self.parse_assoc_item(fn_parse_mode, force_collect)
954956
}
955957

@@ -958,7 +960,7 @@ impl<'a> Parser<'a> {
958960
force_collect: ForceCollect,
959961
) -> PResult<'a, Option<Option<P<AssocItem>>>> {
960962
let fn_parse_mode =
961-
FnParseMode { req_name: |edition| edition >= Edition::Edition2018, req_body: false };
963+
FnParseMode { req_name: |edition, _| edition >= Edition::Edition2018, req_body: false };
962964
self.parse_assoc_item(fn_parse_mode, force_collect)
963965
}
964966

@@ -1235,7 +1237,10 @@ impl<'a> Parser<'a> {
12351237
&mut self,
12361238
force_collect: ForceCollect,
12371239
) -> PResult<'a, Option<Option<P<ForeignItem>>>> {
1238-
let fn_parse_mode = FnParseMode { req_name: |_| true, req_body: false };
1240+
let fn_parse_mode = FnParseMode {
1241+
req_name: |_, is_dot_dot_dot| is_dot_dot_dot == IsDotDotDot::No,
1242+
req_body: false,
1243+
};
12391244
Ok(self.parse_item_(fn_parse_mode, force_collect)?.map(
12401245
|Item { attrs, id, span, vis, kind, tokens }| {
12411246
let kind = match ForeignItemKind::try_from(kind) {
@@ -2107,7 +2112,7 @@ impl<'a> Parser<'a> {
21072112
let inherited_vis =
21082113
Visibility { span: DUMMY_SP, kind: VisibilityKind::Inherited, tokens: None };
21092114
// We use `parse_fn` to get a span for the function
2110-
let fn_parse_mode = FnParseMode { req_name: |_| true, req_body: true };
2115+
let fn_parse_mode = FnParseMode { req_name: |_, _| true, req_body: true };
21112116
match self.parse_fn(
21122117
&mut AttrVec::new(),
21132118
fn_parse_mode,
@@ -2340,8 +2345,16 @@ impl<'a> Parser<'a> {
23402345
/// The function decides if, per-parameter `p`, `p` must have a pattern or just a type.
23412346
///
23422347
/// This function pointer accepts an edition, because in edition 2015, trait declarations
2343-
/// were allowed to omit parameter names. In 2018, they became required.
2344-
type ReqName = fn(Edition) -> bool;
2348+
/// were allowed to omit parameter names. In 2018, they became required. It also accepts an
2349+
/// `IsDotDotDot` parameter, as `extern` function declarations and function pointer types are
2350+
/// allowed to omit the name of the `...` but regular function items are not.
2351+
type ReqName = fn(Edition, IsDotDotDot) -> bool;
2352+
2353+
#[derive(Copy, Clone, PartialEq)]
2354+
pub(crate) enum IsDotDotDot {
2355+
Yes,
2356+
No,
2357+
}
23452358

23462359
/// Parsing configuration for functions.
23472360
///
@@ -2374,6 +2387,8 @@ pub(crate) struct FnParseMode {
23742387
/// to true.
23752388
/// * The span is from Edition 2015. In particular, you can get a
23762389
/// 2015 span inside a 2021 crate using macros.
2390+
///
2391+
/// Or if `IsDotDotDot::Yes`, this function will also return `false` with an `extern` block.
23772392
pub(super) req_name: ReqName,
23782393
/// If this flag is set to `true`, then plain, semicolon-terminated function
23792394
/// prototypes are not allowed here.
@@ -3005,9 +3020,25 @@ impl<'a> Parser<'a> {
30053020
return Ok((res?, Trailing::No, UsePreAttrPos::No));
30063021
}
30073022

3008-
let is_name_required = match this.token.kind {
3009-
token::DotDotDot => false,
3010-
_ => req_name(this.token.span.with_neighbor(this.prev_token.span).edition()),
3023+
let is_dot_dot_dot = if this.token.kind == token::DotDotDot {
3024+
IsDotDotDot::Yes
3025+
} else {
3026+
IsDotDotDot::No
3027+
};
3028+
let is_name_required = req_name(
3029+
this.token.span.with_neighbor(this.prev_token.span).edition(),
3030+
is_dot_dot_dot,
3031+
);
3032+
let is_name_required = if is_name_required && is_dot_dot_dot == IsDotDotDot::Yes {
3033+
this.psess.buffer_lint(
3034+
VARARGS_WITHOUT_PATTERN,
3035+
this.token.span,
3036+
ast::CRATE_NODE_ID,
3037+
BuiltinLintDiag::VarargsWithoutPattern { span: this.token.span },
3038+
);
3039+
false
3040+
} else {
3041+
is_name_required
30113042
};
30123043
let (pat, ty) = if is_name_required || this.is_named_param() {
30133044
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
@@ -400,7 +400,7 @@ impl<'a> Parser<'a> {
400400

401401
let dcx = self.dcx();
402402
let parse_params_result = self.parse_paren_comma_seq(|p| {
403-
let param = p.parse_param_general(|_| false, false, false);
403+
let param = p.parse_param_general(|_, _| false, false, false);
404404
param.map(move |param| {
405405
if !matches!(param.pat.kind, PatKind::Missing) {
406406
dcx.emit_err(FnPathFoundNamedParams {

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, req_body: true },
157+
FnParseMode { req_name: |_, _| true, req_body: true },
158158
force_collect,
159159
)? {
160160
self.mk_stmt(lo.to(item.span), StmtKind::Item(P(item)))

compiler/rustc_parse/src/parser/ty.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ impl<'a> Parser<'a> {
750750
if self.may_recover() && self.token == TokenKind::Lt {
751751
self.recover_fn_ptr_with_generics(lo, &mut params, param_insertion_point)?;
752752
}
753-
let decl = self.parse_fn_decl(|_| false, AllowPlus::No, recover_return_sign)?;
753+
let decl = self.parse_fn_decl(|_, _| false, AllowPlus::No, recover_return_sign)?;
754754

755755
let decl_span = span_start.to(self.prev_token.span);
756756
Ok(TyKind::FnPtr(P(FnPtrTy { ext, safety, generic_params: params, decl, decl_span })))
@@ -1289,7 +1289,7 @@ impl<'a> Parser<'a> {
12891289
self.bump();
12901290
let args_lo = self.token.span;
12911291
let snapshot = self.create_snapshot_for_diagnostic();
1292-
match self.parse_fn_decl(|_| false, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) {
1292+
match self.parse_fn_decl(|_, _| false, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) {
12931293
Ok(decl) => {
12941294
self.dcx().emit_err(ExpectedFnPathFoundFnKeyword { fn_token_span });
12951295
Some(ast::Path {
@@ -1374,7 +1374,7 @@ impl<'a> Parser<'a> {
13741374
// Parse `(T, U) -> R`.
13751375
let inputs_lo = self.token.span;
13761376
let inputs: ThinVec<_> =
1377-
self.parse_fn_params(|_| false)?.into_iter().map(|input| input.ty).collect();
1377+
self.parse_fn_params(|_, _| false)?.into_iter().map(|input| input.ty).collect();
13781378
let inputs_span = inputs_lo.to(self.prev_token.span);
13791379
let output = self.parse_ret_ty(AllowPlus::No, RecoverQPath::No, RecoverReturnSign::No)?;
13801380
let args = ast::ParenthesizedArgs {

0 commit comments

Comments
 (0)