11780: feat: Add type mismatch diagnostic r=flodiebold a=flodiebold

This adds a proper diagnostic for type mismatches, turning "Add reference here", "Missing Ok or Some" and "Remove this semicolon" into quickfixes for this single diagnostic.
The diagnostic is marked as experimental when it does not have one of these quickfixes, so it can be turned off with `rust-analyzer.diagnostics.enableExperimental` (or specifically with `rust-analyzer.diagnostics.disabled` of course, the ID is `type-mismatch`).
There will still be some false positives, but I think there shouldn't be too many especially when the Chalk fix lands, and it's still experimental anyway 🙂 
This also fixes type checking for `rustc_legacy_const_generics` just to avoid some errors in tests.

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
bors[bot] 2022-03-22 10:40:23 +00:00 committed by GitHub
commit 5d2cd18765
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 772 additions and 688 deletions

View file

@ -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<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,
@ -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<AstPtr<ast::Expr>>,
pub mutability: Mutability,
pub expected: Type,
pub actual: Type,
}
pub use hir_ty::diagnostics::IncorrectCase;

View file

@ -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)
}
}

View file

@ -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)]

View file

@ -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,
}
}

View file

@ -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;

View file

@ -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),

View file

@ -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);

View file

@ -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>(

View file

@ -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::{

View file

@ -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);
}
"#,
);
}

View file

@ -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;
}
"#,
);
}
}

View file

@ -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 };
}
"#,
);

View file

@ -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) { () => () }

View file

@ -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 }
"#,
);
}
}

View file

@ -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 }"#);
}
}

View file

@ -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 }"#);
}
}

View file

@ -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),