Skip to content

Commit 02e1f44

Browse files
beetreesfolkertdev
authored andcommitted
c_variadic: Add future-incompatibility warning for ... arguments without a pattern outside of extern blocks
1 parent a7b3715 commit 02e1f44

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
@@ -838,7 +838,7 @@ impl<'a> Parser<'a> {
838838
self.recover_fn_ptr_with_generics(lo, &mut params, param_insertion_point)?;
839839
}
840840
let mode = crate::parser::item::FnParseMode {
841-
req_name: |_| false,
841+
req_name: |_, _| false,
842842
context: FnContext::Free,
843843
req_body: false,
844844
};
@@ -1394,7 +1394,8 @@ impl<'a> Parser<'a> {
13941394
self.bump();
13951395
let args_lo = self.token.span;
13961396
let snapshot = self.create_snapshot_for_diagnostic();
1397-
let mode = FnParseMode { req_name: |_| false, context: FnContext::Free, req_body: false };
1397+
let mode =
1398+
FnParseMode { req_name: |_, _| false, context: FnContext::Free, req_body: false };
13981399
match self.parse_fn_decl(&mode, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) {
13991400
Ok(decl) => {
14001401
self.dcx().emit_err(ExpectedFnPathFoundFnKeyword { fn_token_span });
@@ -1485,7 +1486,8 @@ impl<'a> Parser<'a> {
14851486

14861487
// Parse `(T, U) -> R`.
14871488
let inputs_lo = self.token.span;
1488-
let mode = FnParseMode { req_name: |_| false, context: FnContext::Free, req_body: false };
1489+
let mode =
1490+
FnParseMode { req_name: |_, _| false, context: FnContext::Free, req_body: false };
14891491
let inputs: ThinVec<_> =
14901492
self.parse_fn_params(&mode)?.into_iter().map(|input| input.ty).collect();
14911493
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)