From fc2b395e0095236e3c312974fa1e52a467c26324 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 28 Feb 2023 15:13:45 +0100 Subject: [PATCH 1/4] Show pattern mismatch diagnostics --- crates/hir-expand/src/lib.rs | 9 ++ crates/hir-ty/src/infer.rs | 9 +- crates/hir-ty/src/infer/pat.rs | 7 +- crates/hir-ty/src/tests.rs | 29 +----- crates/hir-ty/src/tests/patterns.rs | 16 +++ crates/hir/src/diagnostics.rs | 3 +- crates/hir/src/lib.rs | 18 +++- .../src/handlers/type_mismatch.rs | 97 +++++++++++++------ 8 files changed, 117 insertions(+), 71 deletions(-) diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index a52716cc02..e4719237a2 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -771,6 +771,15 @@ impl InFile> { } } +impl InFile> { + pub fn transpose(self) -> Either, InFile> { + match self.value { + Either::Left(l) => Either::Left(InFile::new(self.file_id, l)), + Either::Right(r) => Either::Right(InFile::new(self.file_id, r)), + } + } +} + impl<'a> InFile<&'a SyntaxNode> { pub fn ancestors_with_macros( self, diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index c77a5e0737..6790be64c5 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -389,18 +389,15 @@ impl InferenceResult { pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> { self.type_mismatches.get(&pat.into()) } + pub fn type_mismatches(&self) -> impl Iterator { + self.type_mismatches.iter().map(|(expr_or_pat, mismatch)| (*expr_or_pat, mismatch)) + } pub fn expr_type_mismatches(&self) -> impl Iterator { self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { ExprOrPatId::ExprId(expr) => Some((expr, mismatch)), _ => None, }) } - pub fn pat_type_mismatches(&self) -> impl Iterator { - self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { - ExprOrPatId::PatId(pat) => Some((pat, mismatch)), - _ => None, - }) - } } impl Index for InferenceResult { diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 2c935733c0..8b381f0d1f 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -196,12 +196,7 @@ impl<'a> InferenceContext<'a> { Pat::Ref { pat, mutability } => { let mutability = lower_to_chalk_mutability(*mutability); let expectation = match expected.as_reference() { - Some((inner_ty, _lifetime, exp_mut)) => { - if mutability != exp_mut { - // FIXME: emit type error? - } - inner_ty.clone() - } + Some((inner_ty, _lifetime, exp_mut)) => inner_ty.clone(), _ => self.result.standard_types.unknown.clone(), }; let subty = self.infer_pat(*pat, &expectation, default_bm); diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index ba5d9c2412..ab848a18eb 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -191,30 +191,11 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } } - for (pat, mismatch) in inference_result.pat_type_mismatches() { - let node = match pat_node(&body_source_map, pat, &db) { - Some(value) => value, - None => continue, - }; - let range = node.as_ref().original_file_range(&db); - let actual = format!( - "expected {}, got {}", - mismatch.expected.display_test(&db), - mismatch.actual.display_test(&db) - ); - match mismatches.remove(&range) { - Some(annotation) => assert_eq!(actual, annotation), - None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual), - } - } - for (expr, mismatch) in inference_result.expr_type_mismatches() { - let node = match body_source_map.expr_syntax(expr) { - Ok(sp) => { - let root = db.parse_or_expand(sp.file_id).unwrap(); - sp.map(|ptr| ptr.to_node(&root).syntax().clone()) - } - Err(SyntheticSyntax) => continue, - }; + for (expr_or_pat, mismatch) in inference_result.type_mismatches() { + let Some(node) = (match expr_or_pat { + hir_def::expr::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db), + hir_def::expr::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db), + }) else { continue; }; let range = node.as_ref().original_file_range(&db); let actual = format!( "expected {}, got {}", diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index 9333e26935..aa1b2a1d9b 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -1092,3 +1092,19 @@ fn my_fn(foo: ...) {} "#, ); } + +#[test] +fn ref_pat_mutability() { + check( + r#" +fn foo() { + let &() = &(); + let &mut () = &mut (); + let &mut () = &(); + //^^^^^^^ expected &(), got &mut () + let &() = &mut (); + //^^^ expected &mut (), got &() +} +"#, + ); +} diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 54d43fa8dc..3b2591e8a1 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -178,8 +178,7 @@ pub struct MissingMatchArms { #[derive(Debug)] pub struct TypeMismatch { - // FIXME: add mismatches in patterns as well - pub expr: InFile>, + pub expr_or_pat: Either>, InFile>>, pub expected: Type, pub actual: Type, } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c64106d3af..c103244b4e 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1413,14 +1413,22 @@ impl DefWithBody { } } } - for (expr, mismatch) in infer.expr_type_mismatches() { - let expr = match source_map.expr_syntax(expr) { - Ok(expr) => expr, - Err(SyntheticSyntax) => continue, + for (pat_or_expr, mismatch) in infer.type_mismatches() { + let expr_or_pat = match pat_or_expr { + ExprOrPatId::ExprId(expr) => source_map.expr_syntax(expr).map(Either::Left), + ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right), }; + let expr_or_pat = match expr_or_pat { + Ok(Either::Left(expr)) => Either::Left(expr), + Ok(Either::Right(InFile { file_id, value: Either::Left(pat) })) => { + Either::Right(InFile { file_id, value: pat }) + } + Ok(Either::Right(_)) | Err(SyntheticSyntax) => continue, + }; + acc.push( TypeMismatch { - expr, + expr_or_pat, expected: Type::new(db, DefWithBodyId::from(self), mismatch.expected.clone()), actual: Type::new(db, DefWithBodyId::from(self), mismatch.actual.clone()), } diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 2adae165e4..948ca4f632 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -1,8 +1,9 @@ -use hir::{db::AstDatabase, HirDisplay, Type}; +use either::Either; +use hir::{db::AstDatabase, HirDisplay, InFile, Type}; use ide_db::{famous_defs::FamousDefs, source_change::SourceChange}; use syntax::{ ast::{self, BlockExpr, ExprStmt}, - AstNode, + AstNode, AstPtr, }; use text_edit::TextEdit; @@ -10,19 +11,23 @@ use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext} // Diagnostic: type-mismatch // -// This diagnostic is triggered when the type of an expression does not match +// This diagnostic is triggered when the type of an expression or pattern does not match // the expected type. pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic { - let display_range = adjusted_display_range::( - ctx, - d.expr.clone().map(|it| it.into()), - &|block| { - let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range(); - cov_mark::hit!(type_mismatch_on_block); - Some(r_curly_range) - }, - ); - + let display_range = match &d.expr_or_pat { + Either::Left(expr) => adjusted_display_range::( + ctx, + expr.clone().map(|it| it.into()), + &|block| { + let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range(); + cov_mark::hit!(type_mismatch_on_block); + Some(r_curly_range) + }, + ), + Either::Right(pat) => { + ctx.sema.diagnostics_display_range(pat.clone().map(|it| it.into())).range + } + }; let mut diag = Diagnostic::new( "type-mismatch", format!( @@ -42,10 +47,15 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option> { let mut fixes = Vec::new(); - add_reference(ctx, d, &mut fixes); - add_missing_ok_or_some(ctx, d, &mut fixes); - remove_semicolon(ctx, d, &mut fixes); - str_ref_to_owned(ctx, d, &mut fixes); + match &d.expr_or_pat { + Either::Left(expr_ptr) => { + add_reference(ctx, d, expr_ptr, &mut fixes); + add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes); + remove_semicolon(ctx, d, expr_ptr, &mut fixes); + str_ref_to_owned(ctx, d, expr_ptr, &mut fixes); + } + Either::Right(_pat_ptr) => (), + } if fixes.is_empty() { None @@ -53,13 +63,22 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option, + d: &hir::TypeMismatch, + expr_ptr: &InFile>, + acc: &mut Vec, +) -> Option<()> { + None +} fn add_reference( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { - let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range; + let range = ctx.sema.diagnostics_display_range(expr_ptr.clone().map(|it| it.into())).range; let (_, mutability) = d.expected.as_reference()?; let actual_with_ref = Type::reference(&d.actual, mutability); @@ -71,7 +90,7 @@ fn add_reference( let edit = TextEdit::insert(range.start(), ampersands); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("add_reference_here", "Add reference here", source_change, range)); Some(()) } @@ -79,10 +98,11 @@ fn add_reference( fn add_missing_ok_or_some( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); let expr_range = expr.syntax().text_range(); let scope = ctx.sema.scope(expr.syntax())?; @@ -109,7 +129,7 @@ fn add_missing_ok_or_some( builder.insert(expr.syntax().text_range().start(), format!("{variant_name}(")); builder.insert(expr.syntax().text_range().end(), ")".to_string()); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish()); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), builder.finish()); let name = format!("Wrap in {variant_name}"); acc.push(fix("wrap_in_constructor", &name, source_change, expr_range)); Some(()) @@ -118,10 +138,11 @@ fn add_missing_ok_or_some( fn remove_semicolon( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); if !d.actual.is_unit() { return None; } @@ -136,7 +157,7 @@ fn remove_semicolon( let edit = TextEdit::delete(semicolon_range); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range)); Some(()) @@ -145,24 +166,26 @@ fn remove_semicolon( fn str_ref_to_owned( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { let expected = d.expected.display(ctx.sema.db); let actual = d.actual.display(ctx.sema.db); + // FIXME do this properly if expected.to_string() != "String" || actual.to_string() != "&str" { return None; } - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); let expr_range = expr.syntax().text_range(); let to_owned = format!(".to_owned()"); let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range)); Some(()) @@ -592,6 +615,24 @@ fn f() -> i32 { let _ = x + y; } //^ error: expected i32, found () +"#, + ); + } + + #[test] + fn type_mismatch_pat_smoke_test() { + check_diagnostics( + r#" +fn f() { + let &() = &mut (); + //^^^ error: expected &mut (), found &() + match &() { + &9 => () + //^^ error: expected &(), found &i32 + //^ error: expected (), found i32 + //^ error: expected (), found i32 + } +} "#, ); } From ec273c3d12b7393f6b81e793ce1c7abd59e3dc67 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 10:23:20 +0100 Subject: [PATCH 2/4] Split pattern inference into more functions --- crates/hir-def/src/resolver.rs | 2 +- crates/hir-ty/src/infer.rs | 40 +-- crates/hir-ty/src/infer/expr.rs | 47 ++-- crates/hir-ty/src/infer/pat.rs | 300 +++++++++++++++-------- crates/hir-ty/src/tests/patterns.rs | 12 +- crates/ide/src/inlay_hints/adjustment.rs | 5 +- 6 files changed, 234 insertions(+), 172 deletions(-) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 36d8b24e9c..fdb236c111 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -85,7 +85,7 @@ pub enum ResolveValueResult { Partial(TypeNs, usize), } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ValueNs { ImplSelf(ImplId), LocalBinding(PatId), diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 6790be64c5..f229bf2f64 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -144,44 +144,6 @@ impl Default for BindingMode { } } -/// Used to generalize patterns and assignee expressions. -trait PatLike: Into + Copy { - type BindingMode: Copy; - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - default_bm: Self::BindingMode, - ) -> Ty; -} - -impl PatLike for ExprId { - type BindingMode = (); - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - _: Self::BindingMode, - ) -> Ty { - this.infer_assignee_expr(id, expected_ty) - } -} - -impl PatLike for PatId { - type BindingMode = BindingMode; - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - default_bm: Self::BindingMode, - ) -> Ty { - this.infer_pat(id, expected_ty, default_bm) - } -} - #[derive(Debug)] pub(crate) struct InferOk { value: T, @@ -581,7 +543,7 @@ impl<'a> InferenceContext<'a> { let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); - self.infer_pat(*pat, &ty, BindingMode::default()); + self.infer_top_pat(*pat, &ty); } let error_ty = &TypeRef::Error; let return_ty = if data.has_async_kw() { diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index e169cbef49..a186ae836d 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -25,7 +25,9 @@ use syntax::ast::RangeOp; use crate::{ autoderef::{self, Autoderef}, consteval, - infer::{coerce::CoerceMany, find_continuable, BreakableKind}, + infer::{ + coerce::CoerceMany, find_continuable, pat::contains_explicit_ref_binding, BreakableKind, + }, lower::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, @@ -39,8 +41,8 @@ use crate::{ }; use super::{ - coerce::auto_deref_adjust_steps, find_breakable, BindingMode, BreakableContext, Diverges, - Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, + coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, Expectation, + InferenceContext, InferenceDiagnostic, TypeMismatch, }; impl<'a> InferenceContext<'a> { @@ -111,7 +113,7 @@ impl<'a> InferenceContext<'a> { } &Expr::Let { pat, expr } => { let input_ty = self.infer_expr(expr, &Expectation::none()); - self.infer_pat(pat, &input_ty, BindingMode::default()); + self.infer_top_pat(pat, &input_ty); self.result.standard_types.bool_.clone() } Expr::Block { statements, tail, label, id: _ } => { @@ -223,7 +225,7 @@ impl<'a> InferenceContext<'a> { let pat_ty = self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item()); - self.infer_pat(pat, &pat_ty, BindingMode::default()); + self.infer_top_pat(pat, &pat_ty); self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); @@ -298,7 +300,7 @@ impl<'a> InferenceContext<'a> { // Now go through the argument patterns for (arg_pat, arg_ty) in args.iter().zip(sig_tys) { - self.infer_pat(*arg_pat, &arg_ty, BindingMode::default()); + self.infer_top_pat(*arg_pat, &arg_ty); } let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); @@ -395,7 +397,8 @@ impl<'a> InferenceContext<'a> { for arm in arms.iter() { self.diverges = Diverges::Maybe; - let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default()); + let input_ty = self.resolve_ty_shallow(&input_ty); + let _pat_ty = self.infer_top_pat(arm.pat, &input_ty); if let Some(guard_expr) = arm.guard { self.infer_expr( guard_expr, @@ -1142,27 +1145,33 @@ impl<'a> InferenceContext<'a> { let decl_ty = type_ref .as_ref() .map(|tr| self.make_ty(tr)) - .unwrap_or_else(|| self.err_ty()); + .unwrap_or_else(|| self.table.new_type_var()); - // Always use the declared type when specified - let mut ty = decl_ty.clone(); - - if let Some(expr) = initializer { - let actual_ty = - self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())); - if decl_ty.is_unknown() { - ty = actual_ty; + let ty = if let Some(expr) = initializer { + let ty = if contains_explicit_ref_binding(&self.body, *pat) { + self.infer_expr(*expr, &Expectation::has_type(decl_ty.clone())) + } else { + self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())) + }; + if type_ref.is_some() { + decl_ty + } else { + ty } - } + } else { + decl_ty + }; + + self.infer_top_pat(*pat, &ty); if let Some(expr) = else_branch { + let previous_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); self.infer_expr_coerce( *expr, &Expectation::HasType(self.result.standard_types.never.clone()), ); + self.diverges = previous_diverges; } - - self.infer_pat(*pat, &ty, BindingMode::default()); } Statement::Expr { expr, .. } => { self.infer_expr(*expr, &Expectation::none()); diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 8b381f0d1f..6481e0b7a7 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -4,7 +4,8 @@ use std::iter::repeat_with; use chalk_ir::Mutability; use hir_def::{ - expr::{BindingAnnotation, Expr, Literal, Pat, PatId}, + body::Body, + expr::{BindingAnnotation, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId, RecordFieldPat}, path::Path, }; use hir_expand::name::Name; @@ -17,7 +18,43 @@ use crate::{ static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind, }; -use super::PatLike; +/// Used to generalize patterns and assignee expressions. +pub(super) trait PatLike: Into + Copy { + type BindingMode: Copy; + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> Ty; +} + +impl PatLike for ExprId { + type BindingMode = (); + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + (): Self::BindingMode, + ) -> Ty { + this.infer_assignee_expr(id, expected_ty) + } +} + +impl PatLike for PatId { + type BindingMode = BindingMode; + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> Ty { + this.infer_pat(id, expected_ty, default_bm) + } +} impl<'a> InferenceContext<'a> { /// Infers type for tuple struct pattern or its corresponding assignee expression. @@ -110,6 +147,7 @@ impl<'a> InferenceContext<'a> { ellipsis: Option, subs: &[T], ) -> Ty { + let expected = self.resolve_ty_shallow(expected); let expectations = match expected.as_tuple() { Some(parameters) => &*parameters.as_slice(Interner), _ => &[], @@ -143,12 +181,11 @@ impl<'a> InferenceContext<'a> { .intern(Interner) } - pub(super) fn infer_pat( - &mut self, - pat: PatId, - expected: &Ty, - mut default_bm: BindingMode, - ) -> Ty { + pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) -> Ty { + self.infer_pat(pat, expected, BindingMode::default()) + } + + fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty { let mut expected = self.resolve_ty_shallow(expected); if is_non_ref_pat(self.body, pat) { @@ -183,25 +220,17 @@ impl<'a> InferenceContext<'a> { self.infer_tuple_pat_like(&expected, default_bm, *ellipsis, args) } Pat::Or(pats) => { - if let Some((first_pat, rest)) = pats.split_first() { - let ty = self.infer_pat(*first_pat, &expected, default_bm); - for pat in rest { - self.infer_pat(*pat, &expected, default_bm); - } - ty - } else { - self.err_ty() + for pat in pats.iter() { + self.infer_pat(*pat, &expected, default_bm); } + expected.clone() } - Pat::Ref { pat, mutability } => { - let mutability = lower_to_chalk_mutability(*mutability); - let expectation = match expected.as_reference() { - Some((inner_ty, _lifetime, exp_mut)) => inner_ty.clone(), - _ => self.result.standard_types.unknown.clone(), - }; - let subty = self.infer_pat(*pat, &expectation, default_bm); - TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) - } + &Pat::Ref { pat, mutability } => self.infer_ref_pat( + pat, + lower_to_chalk_mutability(mutability), + &expected, + default_bm, + ), Pat::TupleStruct { path: p, args: subpats, ellipsis } => self .infer_tuple_struct_pat_like( p.as_deref(), @@ -221,91 +250,17 @@ impl<'a> InferenceContext<'a> { self.infer_path(&resolver, path, pat.into()).unwrap_or_else(|| self.err_ty()) } Pat::Bind { mode, name: _, subpat } => { - let mode = if mode == &BindingAnnotation::Unannotated { - default_bm - } else { - BindingMode::convert(*mode) - }; - self.result.pat_binding_modes.insert(pat, mode); - - let inner_ty = match subpat { - Some(subpat) => self.infer_pat(*subpat, &expected, default_bm), - None => expected, - }; - let inner_ty = self.insert_type_vars_shallow(inner_ty); - - let bound_ty = match mode { - BindingMode::Ref(mutability) => { - TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()) - .intern(Interner) - } - BindingMode::Move => inner_ty.clone(), - }; - self.write_pat_ty(pat, bound_ty); - return inner_ty; + return self.infer_bind_pat(pat, *mode, default_bm, *subpat, &expected); } Pat::Slice { prefix, slice, suffix } => { - let elem_ty = match expected.kind(Interner) { - TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), - _ => self.err_ty(), - }; - - for &pat_id in prefix.iter().chain(suffix.iter()) { - self.infer_pat(pat_id, &elem_ty, default_bm); - } - - if let &Some(slice_pat_id) = slice { - let rest_pat_ty = match expected.kind(Interner) { - TyKind::Array(_, length) => { - let len = try_const_usize(length); - let len = len.and_then(|len| { - len.checked_sub((prefix.len() + suffix.len()) as u128) - }); - TyKind::Array( - elem_ty.clone(), - usize_const(self.db, len, self.resolver.krate()), - ) - } - _ => TyKind::Slice(elem_ty.clone()), - } - .intern(Interner); - self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); - } - - match expected.kind(Interner) { - TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), - _ => TyKind::Slice(elem_ty), - } - .intern(Interner) + self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm) } Pat::Wild => expected.clone(), Pat::Range { start, end } => { let start_ty = self.infer_expr(*start, &Expectation::has_type(expected.clone())); self.infer_expr(*end, &Expectation::has_type(start_ty)) } - &Pat::Lit(expr) => { - // FIXME: using `Option` here is a workaround until we can use if-let chains in stable. - let mut pat_ty = None; - - // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. - if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { - if let Some((inner, ..)) = expected.as_reference() { - let inner = self.resolve_ty_shallow(inner); - if matches!(inner.kind(Interner), TyKind::Slice(_)) { - let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); - let slice_ty = TyKind::Slice(elem_ty).intern(Interner); - let ty = TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty) - .intern(Interner); - self.write_expr_ty(expr, ty.clone()); - pat_ty = Some(ty); - } - } - } - - pat_ty.unwrap_or_else(|| { - self.infer_expr(expr, &Expectation::has_type(expected.clone())) - }) - } + &Pat::Lit(expr) => self.infer_lit_pat(expr, &expected), Pat::Box { inner } => match self.resolve_boxed_box() { Some(box_adt) => { let (inner_ty, alloc_ty) = match expected.as_adt() { @@ -341,6 +296,109 @@ impl<'a> InferenceContext<'a> { self.write_pat_ty(pat, ty.clone()); ty } + + fn infer_ref_pat( + &mut self, + pat: PatId, + mutability: Mutability, + expected: &Ty, + default_bm: BindingMode, + ) -> Ty { + let expectation = match expected.as_reference() { + Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(), + _ => self.result.standard_types.unknown.clone(), + }; + let subty = self.infer_pat(pat, &expectation, default_bm); + TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) + } + + fn infer_bind_pat( + &mut self, + pat: PatId, + mode: BindingAnnotation, + default_bm: BindingMode, + subpat: Option, + expected: &Ty, + ) -> Ty { + let mode = if mode == BindingAnnotation::Unannotated { + default_bm + } else { + BindingMode::convert(mode) + }; + self.result.pat_binding_modes.insert(pat, mode); + + let inner_ty = match subpat { + Some(subpat) => self.infer_pat(subpat, &expected, default_bm), + None => expected.clone(), + }; + let inner_ty = self.insert_type_vars_shallow(inner_ty); + + let bound_ty = match mode { + BindingMode::Ref(mutability) => { + TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner) + } + BindingMode::Move => inner_ty.clone(), + }; + self.write_pat_ty(pat, bound_ty); + return inner_ty; + } + + fn infer_slice_pat( + &mut self, + expected: &Ty, + prefix: &[PatId], + slice: &Option, + suffix: &[PatId], + default_bm: BindingMode, + ) -> Ty { + let elem_ty = match expected.kind(Interner) { + TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), + _ => self.err_ty(), + }; + + for &pat_id in prefix.iter().chain(suffix.iter()) { + self.infer_pat(pat_id, &elem_ty, default_bm); + } + + if let &Some(slice_pat_id) = slice { + let rest_pat_ty = match expected.kind(Interner) { + TyKind::Array(_, length) => { + let len = try_const_usize(length); + let len = + len.and_then(|len| len.checked_sub((prefix.len() + suffix.len()) as u128)); + TyKind::Array(elem_ty.clone(), usize_const(self.db, len, self.resolver.krate())) + } + _ => TyKind::Slice(elem_ty.clone()), + } + .intern(Interner); + self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); + } + + match expected.kind(Interner) { + TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), + _ => TyKind::Slice(elem_ty), + } + .intern(Interner) + } + + fn infer_lit_pat(&mut self, expr: ExprId, expected: &Ty) -> Ty { + // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. + if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { + if let Some((inner, ..)) = expected.as_reference() { + let inner = self.resolve_ty_shallow(inner); + if matches!(inner.kind(Interner), TyKind::Slice(_)) { + let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); + let slice_ty = TyKind::Slice(elem_ty).intern(Interner); + let ty = + TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty).intern(Interner); + self.write_expr_ty(expr, ty.clone()); + return ty; + } + } + } + + self.infer_expr(expr, &Expectation::has_type(expected.clone())) + } } fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool { @@ -365,3 +423,41 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool { Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false, } } + +pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool { + let mut res = false; + walk_pats(body, pat_id, &mut |pat| { + res |= matches!(pat, Pat::Bind { mode: BindingAnnotation::Ref, .. }) + }); + res +} + +fn walk_pats(body: &Body, pat_id: PatId, f: &mut impl FnMut(&Pat)) { + let pat = &body[pat_id]; + f(pat); + match pat { + Pat::Range { .. } + | Pat::Lit(..) + | Pat::Path(..) + | Pat::ConstBlock(..) + | Pat::Wild + | Pat::Missing => {} + &Pat::Bind { subpat, .. } => { + if let Some(subpat) = subpat { + walk_pats(body, subpat, f); + } + } + Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => { + args.iter().copied().for_each(|p| walk_pats(body, p, f)); + } + Pat::Ref { pat, .. } => walk_pats(body, *pat, f), + Pat::Slice { prefix, slice, suffix } => { + let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter()); + total_iter.copied().for_each(|p| walk_pats(body, p, f)); + } + Pat::Record { args, .. } => { + args.iter().for_each(|RecordFieldPat { pat, .. }| walk_pats(body, *pat, f)); + } + Pat::Box { inner } => walk_pats(body, *inner, f), + } +} diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index aa1b2a1d9b..be67329fee 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -953,9 +953,9 @@ fn main() { 42..51 'true | ()': bool 49..51 '()': () 57..59 '{}': () - 68..80 '(() | true,)': ((),) + 68..80 '(() | true,)': (bool,) 69..71 '()': () - 69..78 '() | true': () + 69..78 '() | true': bool 74..78 'true': bool 74..78 'true': bool 84..86 '{}': () @@ -964,19 +964,15 @@ fn main() { 96..102 '_ | ()': bool 100..102 '()': () 108..110 '{}': () - 119..128 '(() | _,)': ((),) + 119..128 '(() | _,)': (bool,) 120..122 '()': () - 120..126 '() | _': () + 120..126 '() | _': bool 125..126 '_': bool 132..134 '{}': () 49..51: expected bool, got () - 68..80: expected (bool,), got ((),) 69..71: expected bool, got () - 69..78: expected bool, got () 100..102: expected bool, got () - 119..128: expected (bool,), got ((),) 120..122: expected bool, got () - 120..126: expected bool, got () "#]], ); } diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs index 188eb7f977..729780fa0c 100644 --- a/crates/ide/src/inlay_hints/adjustment.rs +++ b/crates/ide/src/inlay_hints/adjustment.rs @@ -606,14 +606,13 @@ fn a() { } #[test] - fn bug() { + fn let_stmt_explicit_ty() { check_with_config( InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, r#" fn main() { - // These should be identical, but they are not... - let () = return; + //^^^^^^ let (): () = return; //^^^^^^ } From 44e2c6ea9207264a86d05344b67999d77687edb6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 10:41:09 +0100 Subject: [PATCH 3/4] Don't emit two type mismatches for literal pattern mismatches --- crates/hir-ty/src/infer/pat.rs | 11 ++++++++--- crates/ide-diagnostics/src/handlers/type_mismatch.rs | 11 +---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 6481e0b7a7..4c97eabd9c 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -181,8 +181,8 @@ impl<'a> InferenceContext<'a> { .intern(Interner) } - pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) -> Ty { - self.infer_pat(pat, expected, BindingMode::default()) + pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) { + self.infer_pat(pat, expected, BindingMode::default()); } fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty { @@ -260,7 +260,12 @@ impl<'a> InferenceContext<'a> { let start_ty = self.infer_expr(*start, &Expectation::has_type(expected.clone())); self.infer_expr(*end, &Expectation::has_type(start_ty)) } - &Pat::Lit(expr) => self.infer_lit_pat(expr, &expected), + &Pat::Lit(expr) => { + // Don't emit type mismatches again, the expression lowering already did that. + let ty = self.infer_lit_pat(expr, &expected); + self.write_pat_ty(pat, ty.clone()); + return ty; + } Pat::Box { inner } => match self.resolve_boxed_box() { Some(box_adt) => { let (inner_ty, alloc_ty) = match expected.as_adt() { diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 948ca4f632..2026a2d4b8 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -54,7 +54,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option (), + Either::Right(_pat_ptr) => {} } if fixes.is_empty() { @@ -63,14 +63,6 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option, - d: &hir::TypeMismatch, - expr_ptr: &InFile>, - acc: &mut Vec, -) -> Option<()> { - None -} fn add_reference( ctx: &DiagnosticsContext<'_>, @@ -630,7 +622,6 @@ fn f() { &9 => () //^^ error: expected &(), found &i32 //^ error: expected (), found i32 - //^ error: expected (), found i32 } } "#, From 522823f610391932e2f4162a8c929af2dacaefc4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 11:13:06 +0100 Subject: [PATCH 4/4] Fix text fixtures of missing_match_arms diagnostics --- crates/hir-ty/src/infer/expr.rs | 2 +- crates/hir-ty/src/infer/pat.rs | 3 ++- .../ide-diagnostics/src/handlers/missing_match_arms.rs | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index a186ae836d..6f20f0dc89 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -398,7 +398,7 @@ impl<'a> InferenceContext<'a> { for arm in arms.iter() { self.diverges = Diverges::Maybe; let input_ty = self.resolve_ty_shallow(&input_ty); - let _pat_ty = self.infer_top_pat(arm.pat, &input_ty); + self.infer_top_pat(arm.pat, &input_ty); if let Some(guard_expr) = arm.guard { self.infer_expr( guard_expr, diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 4c97eabd9c..3d03c2a527 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -293,7 +293,8 @@ impl<'a> InferenceContext<'a> { }; // use a new type variable if we got error type here let ty = self.insert_type_vars_shallow(ty); - if !self.unify(&ty, &expected) { + // FIXME: This never check is odd, but required with out we do inference right now + if !expected.is_never() && !self.unify(&ty, &expected) { self.result .type_mismatches .insert(pat.into(), TypeMismatch { expected, actual: ty.clone() }); diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index c24430ce60..6594eed26d 100644 --- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -273,15 +273,20 @@ enum Either2 { C, D } fn main() { match Either::A { Either2::C => (), + // ^^^^^^^^^^ error: expected Either, found Either2 Either2::D => (), + // ^^^^^^^^^^ error: expected Either, found Either2 } match (true, false) { (true, false, true) => (), + // ^^^^^^^^^^^^^^^^^^^ error: expected (bool, bool), found (bool, bool, bool) (true) => (), // ^^^^ error: expected (bool, bool), found bool } match (true, false) { (true,) => {} } + // ^^^^^^^ error: expected (bool, bool), found (bool,) match (0) { () => () } + // ^^ error: expected i32, found () match Unresolved::Bar { Unresolved::Baz => () } } "#, @@ -295,7 +300,9 @@ fn main() { r#" fn main() { match false { true | () => {} } + // ^^ error: expected bool, found () match (false,) { (true | (),) => {} } + // ^^ error: expected bool, found () } "#, ); @@ -1038,12 +1045,12 @@ fn main() { #[test] fn reference_patterns_in_fields() { cov_mark::check_count!(validate_match_bailed_out, 2); - check_diagnostics( r#" fn main() { match (&false,) { (true,) => {} + // ^^^^^^^ error: expected (&bool,), found (bool,) } match (&false,) { (&true,) => {}