Unnamed repository; edit this file 'description' to name the repository.
Diagnose mismatched arg count for tuple struct patterns
Lukas Wirth 2023-09-08
parent 297ed70 · commit 8654a09
-rw-r--r--crates/hir-ty/src/infer.rs5
-rw-r--r--crates/hir-ty/src/infer/pat.rs15
-rw-r--r--crates/hir/src/diagnostics.rs8
-rw-r--r--crates/hir/src/lib.rs24
-rw-r--r--crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs108
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_match_arms.rs3
-rw-r--r--crates/ide-diagnostics/src/lib.rs1
7 files changed, 144 insertions, 20 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 0fb4934444..794d99964e 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 5da0ab76b8..7d88e42e52 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 80c3bcdca8..b8b997cc50 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,
@@ -183,6 +184,13 @@ pub struct PrivateAssocItem {
}
#[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>>,
pub found: Type,
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 91443238c6..47a2a25b6b 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 6238c7e09e..8265e0b1c1 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::<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
+) {}
+"#,
+ )
+ }
}
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index 82a9a3bd55..d5115c2192 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 b1b9b4b8ea..16ddb2aaf3 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)
}