Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir/src/diagnostics.rs25
-rw-r--r--crates/hir/src/lib.rs113
-rw-r--r--crates/hir_def/src/type_ref.rs16
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs129
-rw-r--r--crates/hir_ty/src/infer.rs2
-rw-r--r--crates/hir_ty/src/infer/coerce.rs100
-rw-r--r--crates/hir_ty/src/infer/expr.rs76
-rw-r--r--crates/hir_ty/src/infer/unify.rs18
-rw-r--r--crates/hir_ty/src/lib.rs2
-rw-r--r--crates/hir_ty/src/tests/simple.rs20
-rw-r--r--crates/ide_diagnostics/src/handlers/add_reference_here.rs163
-rw-r--r--crates/ide_diagnostics/src/handlers/field_shorthand.rs12
-rw-r--r--crates/ide_diagnostics/src/handlers/missing_match_arms.rs1
-rw-r--r--crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs223
-rw-r--r--crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs76
-rw-r--r--crates/ide_diagnostics/src/handlers/type_mismatch.rs476
-rw-r--r--crates/ide_diagnostics/src/lib.rs8
17 files changed, 772 insertions, 688 deletions
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,
@@ -148,28 +146,17 @@ pub struct MismatchedArgCount {
}
#[derive(Debug)]
-pub struct RemoveThisSemicolon {
- pub expr: InFile<AstPtr<ast::Expr>>,
-}
-
-#[derive(Debug)]
-pub struct MissingOkOrSomeInTailExpr {
- pub expr: InFile<AstPtr<ast::Expr>>,
- // `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,
pub match_expr: AstPtr<ast::Expr>,
}
#[derive(Debug)]
-pub struct AddReferenceHere {
+pub struct TypeMismatch {
+ // FIXME: add mismatches in patterns as well
pub expr: InFile<AstPtr<ast::Expr>>,
- 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<Enum> {
+ 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<Item = Ty> + 'a {
+ fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Ty> + '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 <https://doc.rust-lang.org/nomicon/coercions.html> 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<InferOk<(Vec<Adjustment>, 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<TraitEnvironment>,
+ tys: &Canonical<(Ty, Ty)>,
+) -> bool {
+ coerce(db, env, tys).is_ok()
+}
+
+pub(crate) fn coerce(
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ tys: &Canonical<(Ty, Ty)>,
+) -> Result<(Vec<Adjustment>, 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<ExprId>,
from_ty: &Ty,
to_ty: &Ty,
- ) -> InferResult<Ty> {
+ ) -> Result<Ty, TypeError> {
+ 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<Adjustment>, 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<Adjustment>,
{
- 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>, 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, &param_tys);
+ self.check_call_arguments(args, &expected_inputs, &param_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, &param_tys);
+ self.check_call_arguments(args, &expected_inputs, &param_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<u32> {
+ 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<Ty> {
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<Interner>]) -> 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<T>(&mut self, canonical: Canonical<T>) -> T::Result
where
T: HasInterner<Interner = Interner> + Fold<Interner> + 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<T>(
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<const N1: &'static str, const N2: bool>(
+ 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<Vec<Assist>> {
- 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<u8, ()> {
-// 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<Vec<Assist>> {
- 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<i32> {
- if y == 0 {
- return None;
- }
- x / y$0
-}
-"#,
- r#"
-fn div(x: i32, y: i32) -> Option<i32> {
- 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<i32> {
- if y == 0 {
- 0
- } else if true {
- 100
- } else {
- None
- }$0
-}
-"#,
- r#"
-fn div(x: i32, y: i32) -> Option<i32> {
- 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<i32, ()> {
- if y == 0 {
- return Err(());
- }
- x / y$0
-}
-"#,
- r#"
-fn div(x: i32, y: i32) -> Result<i32, ()> {
- 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<T>(x: T) -> Result<T, i32> {
- if x == 0 {
- return Err(7);
- }
- $0x
-}
-"#,
- r#"
-fn div<T>(x: T) -> Result<T, i32> {
- 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<T> = Result<T, ()>;
-
-fn div(x: i32, y: i32) -> MyResult<i32> {
- if y == 0 {
- return Err(());
- }
- x $0/ y
-}
-"#,
- r#"
-type MyResult<T> = Result<T, ()>;
-
-fn div(x: i32, y: i32) -> MyResult<i32> {
- 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<FileRange, FileRange> {
- 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<Vec<Assist>> {
- 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<Vec<Assist>> {
+ 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<Assist>,
+) -> 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<Assist>,
+) -> 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<Assist>,
+) -> 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<i32> {
+ if y == 0 {
+ return None;
+ }
+ x / y$0
+}
+"#,
+ r#"
+fn div(x: i32, y: i32) -> Option<i32> {
+ 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<i32> {
+ if y == 0 {
+ 0
+ } else if true {
+ 100
+ } else {
+ None
+ }$0
+}
+"#,
+ r#"
+fn div(x: i32, y: i32) -> Option<i32> {
+ 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<i32, ()> {
+ if y == 0 {
+ return Err(());
+ }
+ x / y$0
+}
+"#,
+ r#"
+fn div(x: i32, y: i32) -> Result<i32, ()> {
+ 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<T>(x: T) -> Result<T, i32> {
+ if x == 0 {
+ return Err(7);
+ }
+ $0x
+}
+"#,
+ r#"
+fn div<T>(x: T) -> Result<T, i32> {
+ 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<T> = Result<T, ()>;
+
+fn div(x: i32, y: i32) -> MyResult<i32> {
+ if y == 0 {
+ return Err(());
+ }
+ x $0/ y
+}
+"#,
+ r#"
+type MyResult<T> = Result<T, ()>;
+
+fn div(x: i32, y: i32) -> MyResult<i32> {
+ 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),