Diagnose mismatched arg count for tuple struct patterns

This commit is contained in:
Lukas Wirth 2023-09-08 14:31:26 +02:00
parent 297ed70a23
commit 8654a098c7
7 changed files with 144 additions and 20 deletions

View File

@ -228,6 +228,11 @@ pub enum InferenceDiagnostic {
expected: usize,
found: usize,
},
MismatchedTupleStructPatArgCount {
pat: ExprOrPatId,
expected: usize,
found: usize,
},
ExpectedFunction {
call_expr: ExprId,
found: Ty,

View File

@ -15,7 +15,8 @@ use crate::{
infer::{BindingMode, Expectation, InferenceContext, TypeMismatch},
lower::lower_to_chalk_mutability,
primitive::UintTy,
static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind,
static_lifetime, InferenceDiagnostic, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt,
TyKind,
};
/// Used to generalize patterns and assignee expressions.
@ -74,6 +75,18 @@ impl InferenceContext<'_> {
if let Some(variant) = def {
self.write_variant_resolution(id.into(), variant);
}
if let Some(var) = &var_data {
let cmp = if ellipsis.is_some() { usize::gt } else { usize::ne };
if cmp(&subs.len(), &var.fields().len()) {
self.push_diagnostic(InferenceDiagnostic::MismatchedTupleStructPatArgCount {
pat: id.into(),
expected: var.fields().len(),
found: subs.len(),
});
}
}
self.unify(&ty, expected);
let substs =

View File

@ -43,6 +43,7 @@ diagnostics![
MacroExpansionParseError,
MalformedDerive,
MismatchedArgCount,
MismatchedTupleStructPatArgCount,
MissingFields,
MissingMatchArms,
MissingUnsafe,
@ -182,6 +183,13 @@ pub struct PrivateAssocItem {
pub item: AssocItem,
}
#[derive(Debug)]
pub struct MismatchedTupleStructPatArgCount {
pub expr_or_pat: InFile<Either<AstPtr<ast::Expr>, AstPtr<ast::Pat>>>,
pub expected: usize,
pub found: usize,
}
#[derive(Debug)]
pub struct ExpectedFunction {
pub call: InFile<AstPtr<ast::Expr>>,

View File

@ -92,9 +92,10 @@ pub use crate::{
diagnostics::{
AnyDiagnostic, BreakOutsideOfLoop, CaseType, ExpectedFunction, InactiveCode,
IncoherentImpl, IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError,
MacroExpansionParseError, MalformedDerive, MismatchedArgCount, MissingFields,
MissingMatchArms, MissingUnsafe, MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem,
PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
MacroExpansionParseError, MalformedDerive, MismatchedArgCount,
MismatchedTupleStructPatArgCount, MissingFields, MissingMatchArms, MissingUnsafe,
MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField,
UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule,
UnresolvedProcMacro, UnusedMut,
@ -1597,6 +1598,23 @@ impl DefWithBody {
.into(),
)
}
&hir_ty::InferenceDiagnostic::MismatchedTupleStructPatArgCount {
pat,
expected,
found,
} => {
let expr_or_pat = match pat {
ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left),
ExprOrPatId::PatId(pat) => source_map
.pat_syntax(pat)
.expect("unexpected synthetic")
.map(|it| it.unwrap_left())
.map(Either::Right),
};
acc.push(
MismatchedTupleStructPatArgCount { expr_or_pat, expected, found }.into(),
)
}
}
}
for (pat_or_expr, mismatch) in infer.type_mismatches() {

View File

@ -1,10 +1,37 @@
use either::Either;
use hir::InFile;
use syntax::{
ast::{self, HasArgList},
AstNode, TextRange,
AstNode, SyntaxNodePtr, TextRange,
};
use crate::{adjusted_display_range, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: mismatched-tuple-struct-pat-arg-count
//
// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
pub(crate) fn mismatched_tuple_struct_pat_arg_count(
ctx: &DiagnosticsContext<'_>,
d: &hir::MismatchedTupleStructPatArgCount,
) -> Diagnostic {
let s = if d.found == 1 { "" } else { "s" };
let s2 = if d.expected == 1 { "" } else { "s" };
let message = format!(
"this pattern has {} field{s}, but the corresponding tuple struct has {} field{s2}",
d.found, d.expected
);
Diagnostic::new(
DiagnosticCode::RustcHardError("E0023"),
message,
invalid_args_range(
ctx,
d.expr_or_pat.clone().map(|it| it.either(Into::into, Into::into)),
d.expected,
d.found,
),
)
}
// Diagnostic: mismatched-arg-count
//
// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
@ -14,31 +41,63 @@ pub(crate) fn mismatched_arg_count(
) -> Diagnostic {
let s = if d.expected == 1 { "" } else { "s" };
let message = format!("expected {} argument{s}, found {}", d.expected, d.found);
Diagnostic::new(DiagnosticCode::RustcHardError("E0107"), message, invalid_args_range(ctx, d))
Diagnostic::new(
DiagnosticCode::RustcHardError("E0107"),
message,
invalid_args_range(ctx, d.call_expr.clone().map(Into::into), d.expected, d.found),
)
}
fn invalid_args_range(ctx: &DiagnosticsContext<'_>, d: &hir::MismatchedArgCount) -> TextRange {
adjusted_display_range::<ast::Expr>(ctx, d.call_expr.clone().map(|it| it.into()), &|expr| {
let arg_list = match expr {
ast::Expr::CallExpr(call) => call.arg_list()?,
ast::Expr::MethodCallExpr(call) => call.arg_list()?,
fn invalid_args_range(
ctx: &DiagnosticsContext<'_>,
source: InFile<SyntaxNodePtr>,
expected: usize,
found: usize,
) -> TextRange {
adjusted_display_range::<Either<ast::Expr, ast::TupleStructPat>>(ctx, source, &|expr| {
let (text_range, r_paren_token, expected_arg) = match expr {
Either::Left(ast::Expr::CallExpr(call)) => {
let arg_list = call.arg_list()?;
(
arg_list.syntax().text_range(),
arg_list.r_paren_token(),
arg_list.args().nth(expected).map(|it| it.syntax().text_range()),
)
}
Either::Left(ast::Expr::MethodCallExpr(call)) => {
let arg_list = call.arg_list()?;
(
arg_list.syntax().text_range(),
arg_list.r_paren_token(),
arg_list.args().nth(expected).map(|it| it.syntax().text_range()),
)
}
Either::Right(pat) => {
let r_paren = pat.r_paren_token()?;
let l_paren = pat.l_paren_token()?;
(
l_paren.text_range().cover(r_paren.text_range()),
Some(r_paren),
pat.fields().nth(expected).map(|it| it.syntax().text_range()),
)
}
_ => return None,
};
if d.found < d.expected {
if d.found == 0 {
return Some(arg_list.syntax().text_range());
if found < expected {
if found == 0 {
return Some(text_range);
}
if let Some(r_paren) = arg_list.r_paren_token() {
if let Some(r_paren) = r_paren_token {
return Some(r_paren.text_range());
}
}
if d.expected < d.found {
if d.expected == 0 {
return Some(arg_list.syntax().text_range());
if expected < found {
if expected == 0 {
return Some(text_range);
}
let zip = arg_list.args().nth(d.expected).zip(arg_list.r_paren_token());
let zip = expected_arg.zip(r_paren_token);
if let Some((arg, r_paren)) = zip {
return Some(arg.syntax().text_range().cover(r_paren.text_range()));
return Some(arg.cover(r_paren.text_range()));
}
}
@ -331,4 +390,21 @@ fn g() {
"#,
)
}
#[test]
fn tuple_struct_pat() {
check_diagnostics(
r#"
struct S(u32, u32);
fn f(
S(a, b, c): S,
// ^^ error: this pattern has 3 fields, but the corresponding tuple struct has 2 fields
S(): S,
// ^^ error: this pattern has 0 fields, but the corresponding tuple struct has 2 fields
S(e, f, .., g, d): S
// ^^^^^^^^^ error: this pattern has 4 fields, but the corresponding tuple struct has 2 fields
) {}
"#,
)
}
}

View File

@ -319,6 +319,7 @@ fn main() {
match Either::A {
Either::A => (),
Either::B() => (),
// ^^ error: this pattern has 0 fields, but the corresponding tuple struct has 1 field
}
}
"#,
@ -334,9 +335,11 @@ enum A { B(isize, isize), C }
fn main() {
match A::B(1, 2) {
A::B(_, _, _) => (),
// ^^ error: this pattern has 3 fields, but the corresponding tuple struct has 2 fields
}
match A::B(1, 2) {
A::C(_) => (),
// ^^^ error: this pattern has 1 field, but the corresponding tuple struct has 0 fields
}
}
"#,

View File

@ -369,6 +369,7 @@ pub fn diagnostics(
AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled),
AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d),
AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
AnyDiagnostic::MismatchedTupleStructPatArgCount(d) => handlers::mismatched_arg_count::mismatched_tuple_struct_pat_arg_count(&ctx, &d),
};
res.push(d)
}