From 8654a098c76ba0cbfa31d7ea668c3de5a48571a1 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 8 Sep 2023 14:31:26 +0200 Subject: [PATCH] Diagnose mismatched arg count for tuple struct patterns --- crates/hir-ty/src/infer.rs | 5 + crates/hir-ty/src/infer/pat.rs | 15 ++- crates/hir/src/diagnostics.rs | 8 ++ crates/hir/src/lib.rs | 24 +++- .../src/handlers/mismatched_arg_count.rs | 108 +++++++++++++++--- .../src/handlers/missing_match_arms.rs | 3 + crates/ide-diagnostics/src/lib.rs | 1 + 7 files changed, 144 insertions(+), 20 deletions(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 0fb4934444b4..794d99964e4f 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -228,6 +228,11 @@ pub enum InferenceDiagnostic { expected: usize, found: usize, }, + MismatchedTupleStructPatArgCount { + pat: ExprOrPatId, + expected: usize, + found: usize, + }, ExpectedFunction { call_expr: ExprId, found: Ty, diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 5da0ab76b880..7d88e42e52a6 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -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 = diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 80c3bcdca8b7..b8b997cc506e 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -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, AstPtr>>, + pub expected: usize, + pub found: usize, +} + #[derive(Debug)] pub struct ExpectedFunction { pub call: InFile>, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 91443238c62d..47a2a25b6b7f 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -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() { diff --git a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs index 6238c7e09e99..8265e0b1c117 100644 --- a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs +++ b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs @@ -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::(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, + expected: usize, + found: usize, +) -> TextRange { + adjusted_display_range::>(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 +) {} +"#, + ) + } } diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index 82a9a3bd5595..d5115c2192bf 100644 --- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -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 } } "#, diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index b1b9b4b8ea5d..16ddb2aaf38e 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -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) }