diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index b5e7d5db63..bc43fb15ee 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -5,7 +5,7 @@ //! be expressed in terms of hir types themselves. use cfg::{CfgExpr, CfgOptions}; use either::Either; -use hir_def::{path::ModPath, type_ref::Mutability}; +use hir_def::path::ModPath; use hir_expand::{name::Name, HirFileId, InFile}; use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; @@ -28,7 +28,6 @@ macro_rules! diagnostics { } diagnostics![ - AddReferenceHere, BreakOutsideOfLoop, InactiveCode, IncorrectCase, @@ -38,11 +37,10 @@ diagnostics![ MismatchedArgCount, MissingFields, MissingMatchArms, - MissingOkOrSomeInTailExpr, MissingUnsafe, NoSuchField, - RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, + TypeMismatch, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, @@ -147,19 +145,6 @@ pub struct MismatchedArgCount { pub found: usize, } -#[derive(Debug)] -pub struct RemoveThisSemicolon { - pub expr: InFile>, -} - -#[derive(Debug)] -pub struct MissingOkOrSomeInTailExpr { - pub expr: InFile>, - // `Some` or `Ok` depending on whether the return type is Result or Option - pub required: String, - pub expected: Type, -} - #[derive(Debug)] pub struct MissingMatchArms { pub file: HirFileId, @@ -167,9 +152,11 @@ pub struct MissingMatchArms { } #[derive(Debug)] -pub struct AddReferenceHere { +pub struct TypeMismatch { + // FIXME: add mismatches in patterns as well pub expr: InFile>, - pub mutability: Mutability, + pub expected: Type, + pub actual: Type, } pub use hir_ty::diagnostics::IncorrectCase; diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 3c12907b82..f4e58d88ed 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -58,7 +58,6 @@ use hir_ty::{ consteval::{ eval_const, unknown_const_as_generic, ComputedExpr, ConstEvalCtx, ConstEvalError, ConstExt, }, - could_unify, diagnostics::BodyValidationDiagnostic, method_resolution::{self, TyFingerprint}, primitive::UintTy, @@ -85,12 +84,11 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{HasAttrs, Namespace}, diagnostics::{ - AddReferenceHere, AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, - InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields, - MissingMatchArms, MissingOkOrSomeInTailExpr, MissingUnsafe, NoSuchField, - RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro, - UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, - UnresolvedProcMacro, + AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget, + MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, + MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch, + UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, + UnresolvedModule, UnresolvedProcMacro, }, has_source::HasSource, semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo}, @@ -1005,6 +1003,24 @@ impl Adt { Type::from_def(db, id.module(db.upcast()).krate(), id) } + /// Turns this ADT into a type with the given type parameters. This isn't + /// the greatest API, FIXME find a better one. + pub fn ty_with_args(self, db: &dyn HirDatabase, args: &[Type]) -> Type { + let id = AdtId::from(self); + let mut it = args.iter().map(|t| t.ty.clone()); + let ty = TyBuilder::def_ty(db, id.into()) + .fill(|x| { + let r = it.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); + match x { + ParamKind::Type => GenericArgData::Ty(r).intern(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + } + }) + .build(); + let krate = id.module(db.upcast()).krate(); + Type::new(db, krate, id, ty) + } + pub fn module(self, db: &dyn HirDatabase) -> Module { match self { Adt::Struct(s) => s.module(db), @@ -1020,6 +1036,14 @@ impl Adt { Adt::Enum(e) => e.name(db), } } + + pub fn as_enum(&self) -> Option { + if let Self::Enum(v) = self { + Some(*v) + } else { + None + } + } } impl HasVisibility for Adt { @@ -1163,6 +1187,30 @@ impl DefWithBody { } } } + for (expr, mismatch) in infer.expr_type_mismatches() { + let expr = match source_map.expr_syntax(expr) { + Ok(expr) => expr, + Err(SyntheticSyntax) => continue, + }; + acc.push( + TypeMismatch { + expr, + expected: Type::new( + db, + krate, + DefWithBodyId::from(self), + mismatch.expected.clone(), + ), + actual: Type::new( + db, + krate, + DefWithBodyId::from(self), + mismatch.actual.clone(), + ), + } + .into(), + ); + } for expr in hir_ty::diagnostics::missing_unsafe(db, self.into()) { match source_map.expr_syntax(expr) { @@ -1259,25 +1307,6 @@ impl DefWithBody { Err(SyntheticSyntax) => (), } } - BodyValidationDiagnostic::RemoveThisSemicolon { expr } => { - match source_map.expr_syntax(expr) { - Ok(expr) => acc.push(RemoveThisSemicolon { expr }.into()), - Err(SyntheticSyntax) => (), - } - } - BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr, required } => { - match source_map.expr_syntax(expr) { - Ok(expr) => acc.push( - MissingOkOrSomeInTailExpr { - expr, - required, - expected: self.body_type(db), - } - .into(), - ), - Err(SyntheticSyntax) => (), - } - } BodyValidationDiagnostic::MissingMatchArms { match_expr } => { match source_map.expr_syntax(match_expr) { Ok(source_ptr) => { @@ -1299,12 +1328,6 @@ impl DefWithBody { Err(SyntheticSyntax) => (), } } - BodyValidationDiagnostic::AddReferenceHere { arg_expr, mutability } => { - match source_map.expr_syntax(arg_expr) { - Ok(expr) => acc.push(AddReferenceHere { expr, mutability }.into()), - Err(SyntheticSyntax) => (), - } - } } } @@ -2618,6 +2641,17 @@ impl Type { Type { krate, env: environment, ty } } + pub fn reference(inner: &Type, m: Mutability) -> Type { + inner.derived( + TyKind::Ref( + if m.is_mut() { hir_ty::Mutability::Mut } else { hir_ty::Mutability::Not }, + hir_ty::static_lifetime(), + inner.ty.clone(), + ) + .intern(Interner), + ) + } + fn new(db: &dyn HirDatabase, krate: CrateId, lexical_env: impl HasResolver, ty: Ty) -> Type { let resolver = lexical_env.resolver(db.upcast()); let environment = resolver @@ -2659,6 +2693,12 @@ impl Type { matches!(self.ty.kind(Interner), TyKind::Ref(..)) } + pub fn as_reference(&self) -> Option<(Type, Mutability)> { + let (ty, _lt, m) = self.ty.as_reference()?; + let m = Mutability::from_mutable(matches!(m, hir_ty::Mutability::Mut)); + Some((self.derived(ty.clone()), m)) + } + pub fn is_slice(&self) -> bool { matches!(self.ty.kind(Interner), TyKind::Slice(..)) } @@ -2900,7 +2940,7 @@ impl Type { self.autoderef_(db).map(move |ty| self.derived(ty)) } - pub fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator + 'a { + fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator + 'a { // There should be no inference vars in types passed here let canonical = hir_ty::replace_errors_with_variables(&self.ty); let environment = self.env.clone(); @@ -3238,7 +3278,12 @@ impl Type { pub fn could_unify_with(&self, db: &dyn HirDatabase, other: &Type) -> bool { let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone())); - could_unify(db, self.env.clone(), &tys) + hir_ty::could_unify(db, self.env.clone(), &tys) + } + + pub fn could_coerce_to(&self, db: &dyn HirDatabase, to: &Type) -> bool { + let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), to.ty.clone())); + hir_ty::could_coerce(db, self.env.clone(), &tys) } } diff --git a/crates/hir_def/src/type_ref.rs b/crates/hir_def/src/type_ref.rs index c6c521f733..8e9336a0cc 100644 --- a/crates/hir_def/src/type_ref.rs +++ b/crates/hir_def/src/type_ref.rs @@ -38,6 +38,22 @@ impl Mutability { Mutability::Mut => "mut ", } } + + /// Returns `true` if the mutability is [`Mut`]. + /// + /// [`Mut`]: Mutability::Mut + #[must_use] + pub fn is_mut(&self) -> bool { + matches!(self, Self::Mut) + } + + /// Returns `true` if the mutability is [`Shared`]. + /// + /// [`Shared`]: Mutability::Shared + #[must_use] + pub fn is_shared(&self) -> bool { + matches!(self, Self::Shared) + } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs index b7d765c59b..71eb7e3995 100644 --- a/crates/hir_ty/src/diagnostics/expr.rs +++ b/crates/hir_ty/src/diagnostics/expr.rs @@ -4,10 +4,7 @@ use std::sync::Arc; -use hir_def::{ - expr::Statement, path::path, resolver::HasResolver, type_ref::Mutability, AssocItemId, - DefWithBodyId, HasModule, -}; +use hir_def::{path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule}; use hir_expand::name; use itertools::Either; use rustc_hash::FxHashSet; @@ -20,7 +17,7 @@ use crate::{ deconstruct_pat::DeconstructedPat, usefulness::{compute_match_usefulness, MatchCheckCtx}, }, - AdtId, InferenceResult, Interner, Ty, TyExt, TyKind, + InferenceResult, Interner, TyExt, }; pub(crate) use hir_def::{ @@ -43,20 +40,9 @@ pub enum BodyValidationDiagnostic { expected: usize, found: usize, }, - RemoveThisSemicolon { - expr: ExprId, - }, - MissingOkOrSomeInTailExpr { - expr: ExprId, - required: String, - }, MissingMatchArms { match_expr: ExprId, }, - AddReferenceHere { - arg_expr: ExprId, - mutability: Mutability, - }, } impl BodyValidationDiagnostic { @@ -116,30 +102,6 @@ impl ExprValidator { }); } } - let body_expr = &body[body.body_expr]; - if let Expr::Block { statements, tail, .. } = body_expr { - if let Some(t) = tail { - self.validate_results_in_tail_expr(body.body_expr, *t, db); - } else if let Some(Statement::Expr { expr: id, .. }) = statements.last() { - self.validate_missing_tail_expr(body.body_expr, *id); - } - } - - let infer = &self.infer; - let diagnostics = &mut self.diagnostics; - - infer - .expr_type_mismatches() - .filter_map(|(expr, mismatch)| { - let (expr_without_ref, mutability) = - check_missing_refs(infer, expr, &mismatch.expected)?; - - Some((expr_without_ref, mutability)) - }) - .for_each(|(arg_expr, mutability)| { - diagnostics - .push(BodyValidationDiagnostic::AddReferenceHere { arg_expr, mutability }); - }); } fn validate_call( @@ -330,66 +292,6 @@ impl ExprValidator { } pattern } - - fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) { - // the mismatch will be on the whole block currently - let mismatch = match self.infer.type_mismatch_for_expr(body_id) { - Some(m) => m, - None => return, - }; - - let core_result_path = path![core::result::Result]; - let core_option_path = path![core::option::Option]; - - let resolver = self.owner.resolver(db.upcast()); - let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) { - Some(it) => it, - _ => return, - }; - let core_option_enum = match resolver.resolve_known_enum(db.upcast(), &core_option_path) { - Some(it) => it, - _ => return, - }; - - let (params, required) = match mismatch.expected.kind(Interner) { - TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), parameters) - if *enum_id == core_result_enum => - { - (parameters, "Ok".to_string()) - } - TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), parameters) - if *enum_id == core_option_enum => - { - (parameters, "Some".to_string()) - } - _ => return, - }; - - if params.len(Interner) > 0 && params.at(Interner, 0).ty(Interner) == Some(&mismatch.actual) - { - self.diagnostics - .push(BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr: id, required }); - } - } - - fn validate_missing_tail_expr(&mut self, body_id: ExprId, possible_tail_id: ExprId) { - let mismatch = match self.infer.type_mismatch_for_expr(body_id) { - Some(m) => m, - None => return, - }; - - let possible_tail_ty = match self.infer.type_of_expr.get(possible_tail_id) { - Some(ty) => ty, - None => return, - }; - - if !mismatch.actual.is_unit() || mismatch.expected != *possible_tail_ty { - return; - } - - self.diagnostics - .push(BodyValidationDiagnostic::RemoveThisSemicolon { expr: possible_tail_id }); - } } struct FilterMapNextChecker { @@ -523,30 +425,3 @@ fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResul walk(pat, body, infer, &mut has_type_mismatches); !has_type_mismatches } - -fn check_missing_refs( - infer: &InferenceResult, - arg: ExprId, - param: &Ty, -) -> Option<(ExprId, Mutability)> { - let arg_ty = infer.type_of_expr.get(arg)?; - - let reference_one = arg_ty.as_reference(); - let reference_two = param.as_reference(); - - match (reference_one, reference_two) { - (None, Some((referenced_ty, _, mutability))) if referenced_ty == arg_ty => { - Some((arg, Mutability::from_mutable(matches!(mutability, chalk_ir::Mutability::Mut)))) - } - (None, Some((referenced_ty, _, mutability))) => match referenced_ty.kind(Interner) { - TyKind::Slice(subst) if matches!(arg_ty.kind(Interner), TyKind::Array(arr_subst, _) if arr_subst == subst) => { - Some(( - arg, - Mutability::from_mutable(matches!(mutability, chalk_ir::Mutability::Mut)), - )) - } - _ => None, - }, - _ => None, - } -} diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index 442774d0be..c91cdec183 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs @@ -44,6 +44,8 @@ use crate::{ // // https://github.com/rust-lang/rust/issues/57411 #[allow(unreachable_pub)] +pub use coerce::could_coerce; +#[allow(unreachable_pub)] pub use unify::could_unify; pub(crate) mod unify; diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs index 528e3ba882..f54440bf5b 100644 --- a/crates/hir_ty/src/infer/coerce.rs +++ b/crates/hir_ty/src/infer/coerce.rs @@ -5,23 +5,26 @@ //! See and //! `librustc_typeck/check/coercion.rs`. -use std::iter; +use std::{iter, sync::Arc}; -use chalk_ir::{cast::Cast, Goal, Mutability, TyVariableKind}; +use chalk_ir::{cast::Cast, BoundVar, Goal, Mutability, TyVariableKind}; use hir_def::{expr::ExprId, lang_item::LangItemTarget}; use stdx::always; use syntax::SmolStr; use crate::{ autoderef::{Autoderef, AutoderefKind}, + db::HirDatabase, infer::{ - Adjust, Adjustment, AutoBorrow, InferOk, InferResult, InferenceContext, OverloadedDeref, - PointerCast, TypeError, TypeMismatch, + Adjust, Adjustment, AutoBorrow, InferOk, InferenceContext, OverloadedDeref, PointerCast, + TypeError, TypeMismatch, }, static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, Guidance, InEnvironment, Interner, - Solution, Substitution, Ty, TyBuilder, TyExt, TyKind, + Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, }; +use super::unify::InferenceTable; + pub(crate) type CoerceResult = Result, Ty)>, TypeError>; /// Do not require any adjustments, i.e. coerce `x -> x`. @@ -84,8 +87,8 @@ impl CoerceMany { }; if let Some(sig) = sig { let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner); - let result1 = ctx.coerce_inner(self.expected_ty.clone(), &target_ty); - let result2 = ctx.coerce_inner(expr_ty.clone(), &target_ty); + let result1 = ctx.table.coerce_inner(self.expected_ty.clone(), &target_ty); + let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty); if let (Ok(result1), Ok(result2)) = (result1, result2) { ctx.table.register_infer_ok(result1); ctx.table.register_infer_ok(result2); @@ -118,6 +121,45 @@ impl CoerceMany { } } +pub fn could_coerce( + db: &dyn HirDatabase, + env: Arc, + tys: &Canonical<(Ty, Ty)>, +) -> bool { + coerce(db, env, tys).is_ok() +} + +pub(crate) fn coerce( + db: &dyn HirDatabase, + env: Arc, + tys: &Canonical<(Ty, Ty)>, +) -> Result<(Vec, Ty), TypeError> { + let mut table = InferenceTable::new(db, env); + let vars = table.fresh_subst(tys.binders.as_slice(Interner)); + let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner); + let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner); + let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars)?; + // default any type vars that weren't unified back to their original bound vars + // (kind of hacky) + let find_var = |iv| { + vars.iter(Interner).position(|v| match v.interned() { + chalk_ir::GenericArgData::Ty(ty) => ty.inference_var(Interner), + chalk_ir::GenericArgData::Lifetime(lt) => lt.inference_var(Interner), + chalk_ir::GenericArgData::Const(c) => c.inference_var(Interner), + } == Some(iv)) + }; + let fallback = |iv, kind, default, binder| match kind { + chalk_ir::VariableKind::Ty(_ty_kind) => find_var(iv) + .map_or(default, |i| BoundVar::new(binder, i).to_ty(Interner).cast(Interner)), + chalk_ir::VariableKind::Lifetime => find_var(iv) + .map_or(default, |i| BoundVar::new(binder, i).to_lifetime(Interner).cast(Interner)), + chalk_ir::VariableKind::Const(ty) => find_var(iv) + .map_or(default, |i| BoundVar::new(binder, i).to_const(Interner, ty).cast(Interner)), + }; + // FIXME also map the types in the adjustments + Ok((adjustments, table.resolve_with_fallback(ty, &fallback))) +} + impl<'a> InferenceContext<'a> { /// Unify two types, but may coerce the first one to the second one /// using "implicit coercion rules" if needed. @@ -126,16 +168,31 @@ impl<'a> InferenceContext<'a> { expr: Option, from_ty: &Ty, to_ty: &Ty, - ) -> InferResult { + ) -> Result { + let from_ty = self.resolve_ty_shallow(from_ty); + let to_ty = self.resolve_ty_shallow(to_ty); + let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty)?; + if let Some(expr) = expr { + self.write_expr_adj(expr, adjustments); + } + Ok(ty) + } +} + +impl<'a> InferenceTable<'a> { + /// Unify two types, but may coerce the first one to the second one + /// using "implicit coercion rules" if needed. + pub(crate) fn coerce( + &mut self, + from_ty: &Ty, + to_ty: &Ty, + ) -> Result<(Vec, Ty), TypeError> { let from_ty = self.resolve_ty_shallow(from_ty); let to_ty = self.resolve_ty_shallow(to_ty); match self.coerce_inner(from_ty, &to_ty) { Ok(InferOk { value: (adjustments, ty), goals }) => { - if let Some(expr) = expr { - self.write_expr_adj(expr, adjustments); - } - self.table.register_infer_ok(InferOk { value: (), goals }); - Ok(InferOk { value: ty, goals: Vec::new() }) + self.register_infer_ok(InferOk { value: (), goals }); + Ok((adjustments, ty)) } Err(e) => { // FIXME deal with error @@ -154,7 +211,7 @@ impl<'a> InferenceContext<'a> { // // here, we would coerce from `!` to `?T`. if let TyKind::InferenceVar(tv, TyVariableKind::General) = to_ty.kind(Interner) { - self.table.set_diverging(*tv, true); + self.set_diverging(*tv, true); } return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]); } @@ -203,8 +260,7 @@ impl<'a> InferenceContext<'a> { where F: FnOnce(Ty) -> Vec, { - self.table - .try_unify(t1, t2) + self.try_unify(t1, t2) .and_then(|InferOk { goals, .. }| success(f(t1.clone()), t1.clone(), goals)) } @@ -259,9 +315,9 @@ impl<'a> InferenceContext<'a> { // details of coercion errors though, so I think it's useful to leave // the structure like it is. - let snapshot = self.table.snapshot(); + let snapshot = self.snapshot(); - let mut autoderef = Autoderef::new(&mut self.table, from_ty.clone()); + let mut autoderef = Autoderef::new(self, from_ty.clone()); let mut first_error = None; let mut found = None; @@ -317,7 +373,7 @@ impl<'a> InferenceContext<'a> { let InferOk { value: ty, goals } = match found { Some(d) => d, None => { - self.table.rollback_to(snapshot); + self.rollback_to(snapshot); let err = first_error.expect("coerce_borrowed_pointer had no error"); return Err(err); } @@ -513,7 +569,7 @@ impl<'a> InferenceContext<'a> { let coerce_from = reborrow.as_ref().map_or_else(|| from_ty.clone(), |(_, adj)| adj.target.clone()); - let krate = self.resolver.krate().unwrap(); + let krate = self.trait_env.krate; let coerce_unsized_trait = match self.db.lang_item(krate, SmolStr::new_inline("coerce_unsized")) { Some(LangItemTarget::TraitId(trait_)) => trait_, @@ -546,7 +602,7 @@ impl<'a> InferenceContext<'a> { match solution { Solution::Unique(v) => { canonicalized.apply_solution( - &mut self.table, + self, Canonical { binders: v.binders, // FIXME handle constraints @@ -556,7 +612,7 @@ impl<'a> InferenceContext<'a> { } Solution::Ambig(Guidance::Definite(subst)) => { // FIXME need to record an obligation here - canonicalized.apply_solution(&mut self.table, subst) + canonicalized.apply_solution(self, subst) } // FIXME actually we maybe should also accept unknown guidance here _ => return Err(TypeError), diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs index ebbce33e01..43cff92f23 100644 --- a/crates/hir_ty/src/infer/expr.rs +++ b/crates/hir_ty/src/infer/expr.rs @@ -28,7 +28,7 @@ use crate::{ lower::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, - mapping::from_chalk, + mapping::{from_chalk, ToChalk}, method_resolution, primitive::{self, UintTy}, static_lifetime, to_chalk_trait_id, @@ -67,7 +67,7 @@ impl<'a> InferenceContext<'a> { let ty = self.infer_expr_inner(expr, expected); if let Some(target) = expected.only_has_type(&mut self.table) { match self.coerce(Some(expr), &ty, &target) { - Ok(res) => res.value, + Ok(res) => res, Err(_) => { self.result .type_mismatches @@ -279,14 +279,16 @@ impl<'a> InferenceContext<'a> { let callee_ty = self.infer_expr(*callee, &Expectation::none()); let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone()); let mut res = None; + let mut derefed_callee = callee_ty.clone(); // manual loop to be able to access `derefs.table` while let Some((callee_deref_ty, _)) = derefs.next() { res = derefs.table.callable_sig(&callee_deref_ty, args.len()); if res.is_some() { + derefed_callee = callee_deref_ty; break; } } - let (param_tys, ret_ty): (Vec, Ty) = match res { + let (param_tys, ret_ty) = match res { Some(res) => { let adjustments = auto_deref_adjust_steps(&derefs); self.write_expr_adj(*callee, adjustments); @@ -294,6 +296,7 @@ impl<'a> InferenceContext<'a> { } None => (Vec::new(), self.err_ty()), }; + let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args); self.register_obligations_for_call(&callee_ty); let expected_inputs = self.expected_inputs_for_expected_output( @@ -302,7 +305,7 @@ impl<'a> InferenceContext<'a> { param_tys.clone(), ); - self.check_call_arguments(args, &expected_inputs, ¶m_tys); + self.check_call_arguments(args, &expected_inputs, ¶m_tys, &indices_to_skip); self.normalize_associated_types_in(ret_ty) } Expr::MethodCall { receiver, args, method_name, generic_args } => self @@ -952,7 +955,7 @@ impl<'a> InferenceContext<'a> { let expected_inputs = self.expected_inputs_for_expected_output(expected, ret_ty.clone(), param_tys.clone()); - self.check_call_arguments(args, &expected_inputs, ¶m_tys); + self.check_call_arguments(args, &expected_inputs, ¶m_tys, &[]); self.normalize_associated_types_in(ret_ty) } @@ -983,24 +986,40 @@ impl<'a> InferenceContext<'a> { } } - fn check_call_arguments(&mut self, args: &[ExprId], expected_inputs: &[Ty], param_tys: &[Ty]) { + fn check_call_arguments( + &mut self, + args: &[ExprId], + expected_inputs: &[Ty], + param_tys: &[Ty], + skip_indices: &[u32], + ) { // Quoting https://github.com/rust-lang/rust/blob/6ef275e6c3cb1384ec78128eceeb4963ff788dca/src/librustc_typeck/check/mod.rs#L3325 -- // We do this in a pretty awful way: first we type-check any arguments // that are not closures, then we type-check the closures. This is so // that we have more information about the types of arguments when we // type-check the functions. This isn't really the right way to do this. for &check_closures in &[false, true] { + let mut skip_indices = skip_indices.into_iter().copied().fuse().peekable(); let param_iter = param_tys.iter().cloned().chain(repeat(self.err_ty())); let expected_iter = expected_inputs .iter() .cloned() .chain(param_iter.clone().skip(expected_inputs.len())); - for ((&arg, param_ty), expected_ty) in args.iter().zip(param_iter).zip(expected_iter) { + for (idx, ((&arg, param_ty), expected_ty)) in + args.iter().zip(param_iter).zip(expected_iter).enumerate() + { let is_closure = matches!(&self.body[arg], Expr::Lambda { .. }); if is_closure != check_closures { continue; } + while skip_indices.peek().map_or(false, |i| *i < idx as u32) { + skip_indices.next(); + } + if skip_indices.peek().copied() == Some(idx as u32) { + continue; + } + // the difference between param_ty and expected here is that // expected is the parameter when the expected *return* type is // taken into account. So in `let _: &[i32] = identity(&[1, 2])` @@ -1140,6 +1159,49 @@ impl<'a> InferenceContext<'a> { } } + /// Returns the argument indices to skip. + fn check_legacy_const_generics(&mut self, callee: Ty, args: &[ExprId]) -> Vec { + let (func, subst) = match callee.kind(Interner) { + TyKind::FnDef(fn_id, subst) => { + let callable = CallableDefId::from_chalk(self.db, *fn_id); + let func = match callable { + CallableDefId::FunctionId(f) => f, + _ => return Vec::new(), + }; + (func, subst) + } + _ => return Vec::new(), + }; + + let data = self.db.function_data(func); + if data.legacy_const_generics_indices.is_empty() { + return Vec::new(); + } + + // only use legacy const generics if the param count matches with them + if data.params.len() + data.legacy_const_generics_indices.len() != args.len() { + return Vec::new(); + } + + // check legacy const parameters + for (subst_idx, arg_idx) in data.legacy_const_generics_indices.iter().copied().enumerate() { + let arg = match subst.at(Interner, subst_idx).constant(Interner) { + Some(c) => c, + None => continue, // not a const parameter? + }; + if arg_idx >= args.len() as u32 { + continue; + } + let _ty = arg.data(Interner).ty.clone(); + let expected = Expectation::none(); // FIXME use actual const ty, when that is lowered correctly + self.infer_expr(args[arg_idx as usize], &expected); + // FIXME: evaluate and unify with the const + } + let mut indices = data.legacy_const_generics_indices.clone(); + indices.sort(); + indices + } + fn builtin_binary_op_return_ty(&mut self, op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Option { let lhs_ty = self.resolve_ty_shallow(&lhs_ty); let rhs_ty = self.resolve_ty_shallow(&rhs_ty); diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs index ef0675d59f..84ca1660af 100644 --- a/crates/hir_ty/src/infer/unify.rs +++ b/crates/hir_ty/src/infer/unify.rs @@ -3,8 +3,8 @@ use std::{fmt, mem, sync::Arc}; use chalk_ir::{ - cast::Cast, fold::Fold, interner::HasInterner, zip::Zip, FloatTy, IntTy, NoSolution, - TyVariableKind, UniverseIndex, + cast::Cast, fold::Fold, interner::HasInterner, zip::Zip, CanonicalVarKind, FloatTy, IntTy, + NoSolution, TyVariableKind, UniverseIndex, }; use chalk_solve::infer::ParameterEnaVariableExt; use ena::unify::UnifyKey; @@ -299,11 +299,23 @@ impl<'a> InferenceTable<'a> { self.resolve_with_fallback_inner(&mut Vec::new(), t, &fallback) } + pub(crate) fn fresh_subst(&mut self, binders: &[CanonicalVarKind]) -> Substitution { + Substitution::from_iter( + Interner, + binders.iter().map(|kind| { + let param_infer_var = + kind.map_ref(|&ui| self.var_unification_table.new_variable(ui)); + param_infer_var.to_generic_arg(Interner) + }), + ) + } + pub(crate) fn instantiate_canonical(&mut self, canonical: Canonical) -> T::Result where T: HasInterner + Fold + std::fmt::Debug, { - self.var_unification_table.instantiate_canonical(Interner, canonical) + let subst = self.fresh_subst(canonical.binders.as_slice(Interner)); + subst.apply(canonical.value, Interner) } fn resolve_with_fallback_inner( diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs index 59e6fe2a04..945b4b0e4a 100644 --- a/crates/hir_ty/src/lib.rs +++ b/crates/hir_ty/src/lib.rs @@ -51,7 +51,7 @@ pub use autoderef::autoderef; pub use builder::{ParamKind, TyBuilder}; pub use chalk_ext::*; pub use infer::{ - could_unify, Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, InferenceResult, + could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, InferenceResult, }; pub use interner::Interner; pub use lower::{ diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs index 0d050f7461..de27c294f6 100644 --- a/crates/hir_ty/src/tests/simple.rs +++ b/crates/hir_ty/src/tests/simple.rs @@ -1,6 +1,6 @@ use expect_test::expect; -use super::{check_infer, check_types}; +use super::{check_infer, check_no_mismatches, check_types}; #[test] fn infer_box() { @@ -2624,3 +2624,21 @@ pub mod prelude { "#, ); } + +#[test] +fn legacy_const_generics() { + check_no_mismatches( + r#" +#[rustc_legacy_const_generics(1, 3)] +fn mixed( + a: u8, + b: i8, +) {} + +fn f() { + mixed(0, "", -1, true); + mixed::<"", true>(0, -1); +} + "#, + ); +} diff --git a/crates/ide_diagnostics/src/handlers/add_reference_here.rs b/crates/ide_diagnostics/src/handlers/add_reference_here.rs deleted file mode 100644 index db24fd6cce..0000000000 --- a/crates/ide_diagnostics/src/handlers/add_reference_here.rs +++ /dev/null @@ -1,163 +0,0 @@ -use hir::db::AstDatabase; -use ide_db::source_change::SourceChange; -use syntax::AstNode; -use text_edit::TextEdit; - -use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; - -// Diagnostic: add-reference-here -// -// This diagnostic is triggered when there's a missing referencing of expression. -pub(crate) fn add_reference_here( - ctx: &DiagnosticsContext<'_>, - d: &hir::AddReferenceHere, -) -> Diagnostic { - Diagnostic::new( - "add-reference-here", - "add reference here", - ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, - ) - .with_fixes(fixes(ctx, d)) -} - -fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::AddReferenceHere) -> Option> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let arg_expr = d.expr.value.to_node(&root); - - let arg_with_ref = format!("&{}{}", d.mutability.as_keyword_for_ref(), arg_expr.syntax()); - - let arg_range = arg_expr.syntax().text_range(); - let edit = TextEdit::replace(arg_range, arg_with_ref); - let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); - - Some(vec![fix("add_reference_here", "Add reference here", source_change, arg_range)]) -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_diagnostics, check_fix}; - - #[test] - fn missing_reference() { - check_diagnostics( - r#" -fn main() { - test(123); - //^^^ 💡 error: add reference here -} -fn test(arg: &i32) {} -"#, - ); - } - - #[test] - fn test_add_reference_to_int() { - check_fix( - r#" -fn main() { - test(123$0); -} -fn test(arg: &i32) {} - "#, - r#" -fn main() { - test(&123); -} -fn test(arg: &i32) {} - "#, - ); - } - - #[test] - fn test_add_mutable_reference_to_int() { - check_fix( - r#" -fn main() { - test($0123); -} -fn test(arg: &mut i32) {} - "#, - r#" -fn main() { - test(&mut 123); -} -fn test(arg: &mut i32) {} - "#, - ); - } - - #[test] - fn test_add_reference_to_array() { - check_fix( - r#" -fn main() { - test($0[1, 2, 3]); -} -fn test(arg: &[i32]) {} - "#, - r#" -fn main() { - test(&[1, 2, 3]); -} -fn test(arg: &[i32]) {} - "#, - ); - } - - #[test] - fn test_add_reference_to_method_call() { - check_fix( - r#" -fn main() { - Test.call_by_ref($0123); -} -struct Test; -impl Test { - fn call_by_ref(&self, arg: &i32) {} -} - "#, - r#" -fn main() { - Test.call_by_ref(&123); -} -struct Test; -impl Test { - fn call_by_ref(&self, arg: &i32) {} -} - "#, - ); - } - - #[test] - fn test_add_reference_to_let_stmt() { - check_fix( - r#" -fn main() { - let test: &i32 = $0123; -} - "#, - r#" -fn main() { - let test: &i32 = &123; -} - "#, - ); - } - - #[test] - fn test_add_mutable_reference_to_let_stmt() { - check_fix( - r#" -fn main() { - let test: &mut i32 = $0123; -} - "#, - r#" -fn main() { - let test: &mut i32 = &mut 123; -} - "#, - ); - } -} diff --git a/crates/ide_diagnostics/src/handlers/field_shorthand.rs b/crates/ide_diagnostics/src/handlers/field_shorthand.rs index 33152e2845..2b71053625 100644 --- a/crates/ide_diagnostics/src/handlers/field_shorthand.rs +++ b/crates/ide_diagnostics/src/handlers/field_shorthand.rs @@ -108,13 +108,13 @@ mod tests { check_diagnostics( r#" struct A { a: &'static str } -fn main() { A { a: "hello" } } +fn main() { A { a: "hello" }; } "#, ); check_diagnostics( r#" struct A(usize); -fn main() { A { 0: 0 } } +fn main() { A { 0: 0 }; } "#, ); @@ -123,14 +123,14 @@ fn main() { A { 0: 0 } } struct A { a: &'static str } fn main() { let a = "haha"; - A { a$0: a } + A { a$0: a }; } "#, r#" struct A { a: &'static str } fn main() { let a = "haha"; - A { a } + A { a }; } "#, ); @@ -141,7 +141,7 @@ struct A { a: &'static str, b: &'static str } fn main() { let a = "haha"; let b = "bb"; - A { a$0: a, b } + A { a$0: a, b }; } "#, r#" @@ -149,7 +149,7 @@ struct A { a: &'static str, b: &'static str } fn main() { let a = "haha"; let b = "bb"; - A { a, b } + A { a, b }; } "#, ); diff --git a/crates/ide_diagnostics/src/handlers/missing_match_arms.rs b/crates/ide_diagnostics/src/handlers/missing_match_arms.rs index 6bdcd41a79..fe6a8683c1 100644 --- a/crates/ide_diagnostics/src/handlers/missing_match_arms.rs +++ b/crates/ide_diagnostics/src/handlers/missing_match_arms.rs @@ -278,6 +278,7 @@ fn main() { match (true, false) { (true, false, true) => (), (true) => (), + // ^^^^ error: expected (bool, bool), found bool } match (true, false) { (true,) => {} } match (0) { () => () } diff --git a/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs b/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs deleted file mode 100644 index d5635ba8ba..0000000000 --- a/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs +++ /dev/null @@ -1,223 +0,0 @@ -use hir::{db::AstDatabase, TypeInfo}; -use ide_db::{ - assists::Assist, source_change::SourceChange, syntax_helpers::node_ext::for_each_tail_expr, -}; -use syntax::AstNode; -use text_edit::TextEdit; - -use crate::{fix, Diagnostic, DiagnosticsContext}; - -// Diagnostic: missing-ok-or-some-in-tail-expr -// -// This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`, -// or if a block that should return `Option` returns a value not wrapped in `Some`. -// -// Example: -// -// ```rust -// fn foo() -> Result { -// 10 -// } -// ``` -pub(crate) fn missing_ok_or_some_in_tail_expr( - ctx: &DiagnosticsContext<'_>, - d: &hir::MissingOkOrSomeInTailExpr, -) -> Diagnostic { - Diagnostic::new( - "missing-ok-or-some-in-tail-expr", - format!("wrap return expression in {}", d.required), - ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, - ) - .with_fixes(fixes(ctx, d)) -} - -fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Option> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let tail_expr = d.expr.value.to_node(&root); - let tail_expr_range = tail_expr.syntax().text_range(); - let mut builder = TextEdit::builder(); - for_each_tail_expr(&tail_expr, &mut |expr| { - if ctx.sema.type_of_expr(expr).map(TypeInfo::original).as_ref() != Some(&d.expected) { - builder.insert(expr.syntax().text_range().start(), format!("{}(", d.required)); - 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()); - let name = if d.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; - Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)]) -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_diagnostics, check_fix}; - - #[test] - fn test_wrap_return_type_option() { - check_fix( - r#" -//- minicore: option, result -fn div(x: i32, y: i32) -> Option { - if y == 0 { - return None; - } - x / y$0 -} -"#, - r#" -fn div(x: i32, y: i32) -> Option { - if y == 0 { - return None; - } - Some(x / y) -} -"#, - ); - } - - #[test] - fn test_wrap_return_type_option_tails() { - check_fix( - r#" -//- minicore: option, result -fn div(x: i32, y: i32) -> Option { - if y == 0 { - 0 - } else if true { - 100 - } else { - None - }$0 -} -"#, - r#" -fn div(x: i32, y: i32) -> Option { - if y == 0 { - Some(0) - } else if true { - Some(100) - } else { - None - } -} -"#, - ); - } - - #[test] - fn test_wrap_return_type() { - check_fix( - r#" -//- minicore: option, result -fn div(x: i32, y: i32) -> Result { - if y == 0 { - return Err(()); - } - x / y$0 -} -"#, - r#" -fn div(x: i32, y: i32) -> Result { - if y == 0 { - return Err(()); - } - Ok(x / y) -} -"#, - ); - } - - #[test] - fn test_wrap_return_type_handles_generic_functions() { - check_fix( - r#" -//- minicore: option, result -fn div(x: T) -> Result { - if x == 0 { - return Err(7); - } - $0x -} -"#, - r#" -fn div(x: T) -> Result { - if x == 0 { - return Err(7); - } - Ok(x) -} -"#, - ); - } - - #[test] - fn test_wrap_return_type_handles_type_aliases() { - check_fix( - r#" -//- minicore: option, result -type MyResult = Result; - -fn div(x: i32, y: i32) -> MyResult { - if y == 0 { - return Err(()); - } - x $0/ y -} -"#, - r#" -type MyResult = Result; - -fn div(x: i32, y: i32) -> MyResult { - if y == 0 { - return Err(()); - } - Ok(x / y) -} -"#, - ); - } - - #[test] - fn test_in_const_and_static() { - check_fix( - r#" -//- minicore: option, result -static A: Option<()> = {($0)}; - "#, - r#" -static A: Option<()> = {Some(())}; - "#, - ); - check_fix( - r#" -//- minicore: option, result -const _: Option<()> = {($0)}; - "#, - r#" -const _: Option<()> = {Some(())}; - "#, - ); - } - - #[test] - fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { - check_diagnostics( - r#" -//- minicore: option, result -fn foo() -> Result<(), i32> { 0 } -"#, - ); - } - - #[test] - fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { - check_diagnostics( - r#" -//- minicore: option, result -enum SomeOtherEnum { Ok(i32), Err(String) } - -fn foo() -> SomeOtherEnum { 0 } -"#, - ); - } -} diff --git a/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs b/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs deleted file mode 100644 index 141fbc42fa..0000000000 --- a/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs +++ /dev/null @@ -1,76 +0,0 @@ -use ide_db::{ - base_db::{FileLoader, FileRange}, - source_change::SourceChange, -}; -use syntax::{TextRange, TextSize}; -use text_edit::TextEdit; - -use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; - -// Diagnostic: remove-this-semicolon -// -// This diagnostic is triggered when there's an erroneous `;` at the end of the block. -pub(crate) fn remove_this_semicolon( - ctx: &DiagnosticsContext<'_>, - d: &hir::RemoveThisSemicolon, -) -> Diagnostic { - Diagnostic::new( - "remove-this-semicolon", - "remove this semicolon", - semicolon_range(ctx, d).unwrap_or_else(|it| it).range, - ) - .with_fixes(fixes(ctx, d)) -} - -fn semicolon_range( - ctx: &DiagnosticsContext<'_>, - d: &hir::RemoveThisSemicolon, -) -> Result { - let expr_range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())); - let file_text = ctx.sema.db.file_text(expr_range.file_id); - let range_end: usize = expr_range.range.end().into(); - // FIXME: This doesn't handle whitespace and comments, but handling those in - // the presence of macros might prove tricky... - if file_text[range_end..].starts_with(';') { - Ok(FileRange { - file_id: expr_range.file_id, - range: TextRange::at(expr_range.range.end(), TextSize::of(';')), - }) - } else { - Err(expr_range) - } -} - -fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::RemoveThisSemicolon) -> Option> { - let semicolon_range = semicolon_range(ctx, d).ok()?; - - let edit = TextEdit::delete(semicolon_range.range); - let source_change = SourceChange::from_text_edit(semicolon_range.file_id, edit); - - Some(vec![fix( - "remove_semicolon", - "Remove this semicolon", - source_change, - semicolon_range.range, - )]) -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_diagnostics, check_fix}; - - #[test] - fn missing_semicolon() { - check_diagnostics( - r#" -fn test() -> i32 { 123; } - //^ 💡 error: remove this semicolon -"#, - ); - } - - #[test] - fn remove_semicolon() { - check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); - } -} diff --git a/crates/ide_diagnostics/src/handlers/type_mismatch.rs b/crates/ide_diagnostics/src/handlers/type_mismatch.rs new file mode 100644 index 0000000000..040dcbd1d9 --- /dev/null +++ b/crates/ide_diagnostics/src/handlers/type_mismatch.rs @@ -0,0 +1,476 @@ +use hir::{db::AstDatabase, HirDisplay, Type, TypeInfo}; +use ide_db::{ + famous_defs::FamousDefs, source_change::SourceChange, + syntax_helpers::node_ext::for_each_tail_expr, +}; +use syntax::{ + ast::{BlockExpr, ExprStmt}, + AstNode, +}; +use text_edit::TextEdit; + +use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; + +// Diagnostic: type-mismatch +// +// This diagnostic is triggered when the type of an expression does not match +// the expected type. +pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic { + let mut diag = Diagnostic::new( + "type-mismatch", + format!( + "expected {}, found {}", + d.expected.display(ctx.sema.db), + d.actual.display(ctx.sema.db) + ), + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) + .with_fixes(fixes(ctx, d)); + if diag.fixes.is_none() { + diag.experimental = true; + } + diag +} + +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); + + if fixes.is_empty() { + None + } else { + Some(fixes) + } +} + +fn add_reference( + ctx: &DiagnosticsContext<'_>, + d: &hir::TypeMismatch, + acc: &mut Vec, +) -> Option<()> { + let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; + let expr_node = d.expr.value.to_node(&root); + + let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range; + + let (_, mutability) = d.expected.as_reference()?; + let actual_with_ref = Type::reference(&d.actual, mutability); + if !actual_with_ref.could_coerce_to(ctx.sema.db, &d.expected) { + return None; + } + + let ampersands = format!("&{}", mutability.as_keyword_for_ref()); + + let edit = TextEdit::insert(expr_node.syntax().text_range().start(), ampersands); + let source_change = + SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + acc.push(fix("add_reference_here", "Add reference here", source_change, range)); + Some(()) +} + +fn add_missing_ok_or_some( + ctx: &DiagnosticsContext<'_>, + d: &hir::TypeMismatch, + acc: &mut Vec, +) -> Option<()> { + let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; + let tail_expr = d.expr.value.to_node(&root); + let tail_expr_range = tail_expr.syntax().text_range(); + let scope = ctx.sema.scope(tail_expr.syntax()); + + let expected_adt = d.expected.as_adt()?; + let expected_enum = expected_adt.as_enum()?; + + let famous_defs = FamousDefs(&ctx.sema, scope.krate()); + let core_result = famous_defs.core_result_Result(); + let core_option = famous_defs.core_option_Option(); + + if Some(expected_enum) != core_result && Some(expected_enum) != core_option { + return None; + } + + let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" }; + + let wrapped_actual_ty = expected_adt.ty_with_args(ctx.sema.db, &[d.actual.clone()]); + + if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) { + return None; + } + + let mut builder = TextEdit::builder(); + for_each_tail_expr(&tail_expr, &mut |expr| { + if ctx.sema.type_of_expr(expr).map(TypeInfo::adjusted).as_ref() != Some(&d.expected) { + 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()); + let name = format!("Wrap in {}", variant_name); + acc.push(fix("wrap_tail_expr", &name, source_change, tail_expr_range)); + Some(()) +} + +fn remove_semicolon( + ctx: &DiagnosticsContext<'_>, + d: &hir::TypeMismatch, + acc: &mut Vec, +) -> Option<()> { + let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; + let expr = d.expr.value.to_node(&root); + if !d.actual.is_unit() { + return None; + } + let block = BlockExpr::cast(expr.syntax().clone())?; + let expr_before_semi = + block.statements().last().and_then(|s| ExprStmt::cast(s.syntax().clone()))?; + let type_before_semi = ctx.sema.type_of_expr(&expr_before_semi.expr()?)?.original(); + if !type_before_semi.could_coerce_to(ctx.sema.db, &d.expected) { + return None; + } + let semicolon_range = expr_before_semi.semicolon_token()?.text_range(); + + let edit = TextEdit::delete(semicolon_range); + let source_change = + SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + + acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range)); + Some(()) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_diagnostics, check_fix, check_no_fix}; + + #[test] + fn missing_reference() { + check_diagnostics( + r#" +fn main() { + test(123); + //^^^ 💡 error: expected &i32, found i32 +} +fn test(arg: &i32) {} +"#, + ); + } + + #[test] + fn test_add_reference_to_int() { + check_fix( + r#" +fn main() { + test(123$0); +} +fn test(arg: &i32) {} + "#, + r#" +fn main() { + test(&123); +} +fn test(arg: &i32) {} + "#, + ); + } + + #[test] + fn test_add_mutable_reference_to_int() { + check_fix( + r#" +fn main() { + test($0123); +} +fn test(arg: &mut i32) {} + "#, + r#" +fn main() { + test(&mut 123); +} +fn test(arg: &mut i32) {} + "#, + ); + } + + #[test] + fn test_add_reference_to_array() { + check_fix( + r#" +//- minicore: coerce_unsized +fn main() { + test($0[1, 2, 3]); +} +fn test(arg: &[i32]) {} + "#, + r#" +fn main() { + test(&[1, 2, 3]); +} +fn test(arg: &[i32]) {} + "#, + ); + } + + #[test] + fn test_add_reference_with_autoderef() { + check_fix( + r#" +//- minicore: coerce_unsized, deref +struct Foo; +struct Bar; +impl core::ops::Deref for Foo { + type Target = Bar; +} + +fn main() { + test($0Foo); +} +fn test(arg: &Bar) {} + "#, + r#" +struct Foo; +struct Bar; +impl core::ops::Deref for Foo { + type Target = Bar; +} + +fn main() { + test(&Foo); +} +fn test(arg: &Bar) {} + "#, + ); + } + + #[test] + fn test_add_reference_to_method_call() { + check_fix( + r#" +fn main() { + Test.call_by_ref($0123); +} +struct Test; +impl Test { + fn call_by_ref(&self, arg: &i32) {} +} + "#, + r#" +fn main() { + Test.call_by_ref(&123); +} +struct Test; +impl Test { + fn call_by_ref(&self, arg: &i32) {} +} + "#, + ); + } + + #[test] + fn test_add_reference_to_let_stmt() { + check_fix( + r#" +fn main() { + let test: &i32 = $0123; +} + "#, + r#" +fn main() { + let test: &i32 = &123; +} + "#, + ); + } + + #[test] + fn test_add_mutable_reference_to_let_stmt() { + check_fix( + r#" +fn main() { + let test: &mut i32 = $0123; +} + "#, + r#" +fn main() { + let test: &mut i32 = &mut 123; +} + "#, + ); + } + + #[test] + fn test_wrap_return_type_option() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) -> Option { + if y == 0 { + return None; + } + x / y$0 +} +"#, + r#" +fn div(x: i32, y: i32) -> Option { + if y == 0 { + return None; + } + Some(x / y) +} +"#, + ); + } + + #[test] + fn test_wrap_return_type_option_tails() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) -> Option { + if y == 0 { + 0 + } else if true { + 100 + } else { + None + }$0 +} +"#, + r#" +fn div(x: i32, y: i32) -> Option { + if y == 0 { + Some(0) + } else if true { + Some(100) + } else { + None + } +} +"#, + ); + } + + #[test] + fn test_wrap_return_type() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) -> Result { + if y == 0 { + return Err(()); + } + x / y$0 +} +"#, + r#" +fn div(x: i32, y: i32) -> Result { + if y == 0 { + return Err(()); + } + Ok(x / y) +} +"#, + ); + } + + #[test] + fn test_wrap_return_type_handles_generic_functions() { + check_fix( + r#" +//- minicore: option, result +fn div(x: T) -> Result { + if x == 0 { + return Err(7); + } + $0x +} +"#, + r#" +fn div(x: T) -> Result { + if x == 0 { + return Err(7); + } + Ok(x) +} +"#, + ); + } + + #[test] + fn test_wrap_return_type_handles_type_aliases() { + check_fix( + r#" +//- minicore: option, result +type MyResult = Result; + +fn div(x: i32, y: i32) -> MyResult { + if y == 0 { + return Err(()); + } + x $0/ y +} +"#, + r#" +type MyResult = Result; + +fn div(x: i32, y: i32) -> MyResult { + if y == 0 { + return Err(()); + } + Ok(x / y) +} +"#, + ); + } + + #[test] + fn test_in_const_and_static() { + check_fix( + r#" +//- minicore: option, result +static A: Option<()> = {($0)}; + "#, + r#" +static A: Option<()> = {Some(())}; + "#, + ); + check_fix( + r#" +//- minicore: option, result +const _: Option<()> = {($0)}; + "#, + r#" +const _: Option<()> = {Some(())}; + "#, + ); + } + + #[test] + fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { + check_no_fix( + r#" +//- minicore: option, result +fn foo() -> Result<(), i32> { 0$0 } +"#, + ); + } + + #[test] + fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { + check_no_fix( + r#" +//- minicore: option, result +enum SomeOtherEnum { Ok(i32), Err(String) } + +fn foo() -> SomeOtherEnum { 0$0 } +"#, + ); + } + + #[test] + fn remove_semicolon() { + check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); + } +} diff --git a/crates/ide_diagnostics/src/lib.rs b/crates/ide_diagnostics/src/lib.rs index 86d76751ad..13ac0a2450 100644 --- a/crates/ide_diagnostics/src/lib.rs +++ b/crates/ide_diagnostics/src/lib.rs @@ -24,7 +24,6 @@ //! don't yet have a great pattern for how to do them properly. mod handlers { - pub(crate) mod add_reference_here; pub(crate) mod break_outside_of_loop; pub(crate) mod inactive_code; pub(crate) mod incorrect_case; @@ -34,11 +33,10 @@ mod handlers { pub(crate) mod mismatched_arg_count; pub(crate) mod missing_fields; pub(crate) mod missing_match_arms; - pub(crate) mod missing_ok_or_some_in_tail_expr; pub(crate) mod missing_unsafe; pub(crate) mod no_such_field; - pub(crate) mod remove_this_semicolon; pub(crate) mod replace_filter_map_next_with_find_map; + pub(crate) mod type_mismatch; pub(crate) mod unimplemented_builtin_macro; pub(crate) mod unresolved_extern_crate; pub(crate) mod unresolved_import; @@ -191,7 +189,6 @@ pub fn diagnostics( for diag in diags { #[rustfmt::skip] let d = match diag { - AnyDiagnostic::AddReferenceHere(d) => handlers::add_reference_here::add_reference_here(&ctx, &d), AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), @@ -199,11 +196,10 @@ pub fn diagnostics( AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d), AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d), AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d), - AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => handlers::missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d), AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d), AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d), - AnyDiagnostic::RemoveThisSemicolon(d) => handlers::remove_this_semicolon::remove_this_semicolon(&ctx, &d), AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d), + AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d), AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), AnyDiagnostic::UnresolvedExternCrate(d) => handlers::unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), AnyDiagnostic::UnresolvedImport(d) => handlers::unresolved_import::unresolved_import(&ctx, &d),