mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 05:53:45 +00:00
Diagnose incorrect usages of the question mark operator
This commit is contained in:
parent
8406380b5a
commit
381366f1dd
13 changed files with 289 additions and 125 deletions
|
@ -259,6 +259,7 @@ macro_rules! __known_path {
|
||||||
(core::future::Future) => {};
|
(core::future::Future) => {};
|
||||||
(core::future::IntoFuture) => {};
|
(core::future::IntoFuture) => {};
|
||||||
(core::ops::Try) => {};
|
(core::ops::Try) => {};
|
||||||
|
(core::ops::FromResidual) => {};
|
||||||
($path:path) => {
|
($path:path) => {
|
||||||
compile_error!("Please register your known path in the path module")
|
compile_error!("Please register your known path in the path module")
|
||||||
};
|
};
|
||||||
|
|
|
@ -279,6 +279,8 @@ pub mod known {
|
||||||
RangeToInclusive,
|
RangeToInclusive,
|
||||||
RangeTo,
|
RangeTo,
|
||||||
Range,
|
Range,
|
||||||
|
Residual,
|
||||||
|
FromResidual,
|
||||||
Neg,
|
Neg,
|
||||||
Not,
|
Not,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -190,7 +190,9 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
|
||||||
pub enum InferenceDiagnostic {
|
pub enum InferenceDiagnostic {
|
||||||
NoSuchField { expr: ExprId },
|
NoSuchField { expr: ExprId },
|
||||||
BreakOutsideOfLoop { expr: ExprId, is_break: bool },
|
BreakOutsideOfLoop { expr: ExprId, is_break: bool },
|
||||||
|
IncorrectTryTarget { expr: ExprId },
|
||||||
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
|
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
|
||||||
|
DoesNotImplement { expr: ExprId, trait_: TraitId, ty: Ty },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mismatch between an expected and an inferred type.
|
/// A mismatch between an expected and an inferred type.
|
||||||
|
@ -905,17 +907,6 @@ impl<'a> InferenceContext<'a> {
|
||||||
self.db.trait_data(trait_).associated_type_by_name(&name![Item])
|
self.db.trait_data(trait_).associated_type_by_name(&name![Item])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
|
|
||||||
// FIXME resolve via lang_item once try v2 is stable
|
|
||||||
let path = path![core::ops::Try];
|
|
||||||
let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
|
|
||||||
let trait_data = self.db.trait_data(trait_);
|
|
||||||
trait_data
|
|
||||||
// FIXME remove once try v2 is stable
|
|
||||||
.associated_type_by_name(&name![Ok])
|
|
||||||
.or_else(|| trait_data.associated_type_by_name(&name![Output]))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
|
fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
|
||||||
let trait_ = self.resolve_lang_item(name![neg])?.as_trait()?;
|
let trait_ = self.resolve_lang_item(name![neg])?.as_trait()?;
|
||||||
self.db.trait_data(trait_).associated_type_by_name(&name![Output])
|
self.db.trait_data(trait_).associated_type_by_name(&name![Output])
|
||||||
|
|
|
@ -19,24 +19,24 @@ use hir_def::{
|
||||||
resolver::resolver_for_expr,
|
resolver::resolver_for_expr,
|
||||||
ConstParamId, FieldId, ItemContainerId, Lookup,
|
ConstParamId, FieldId, ItemContainerId, Lookup,
|
||||||
};
|
};
|
||||||
use hir_expand::name::Name;
|
use hir_expand::{name, name::Name};
|
||||||
use stdx::always;
|
use stdx::always;
|
||||||
use syntax::ast::RangeOp;
|
use syntax::ast::RangeOp;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
autoderef::{self, Autoderef},
|
autoderef::{self, Autoderef},
|
||||||
consteval,
|
consteval,
|
||||||
infer::{coerce::CoerceMany, find_continuable, BreakableKind},
|
infer::{coerce::CoerceMany, find_continuable, path, BreakableKind},
|
||||||
lower::{
|
lower::{
|
||||||
const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
|
const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
|
||||||
},
|
},
|
||||||
mapping::{from_chalk, ToChalk},
|
mapping::{from_chalk, ToChalk},
|
||||||
method_resolution::{self, lang_names_for_bin_op, VisibleFromModule},
|
method_resolution::{self, lang_names_for_bin_op, VisibleFromModule},
|
||||||
primitive::{self, UintTy},
|
primitive::{self, UintTy},
|
||||||
static_lifetime, to_chalk_trait_id,
|
static_lifetime, to_assoc_type_id, to_chalk_trait_id,
|
||||||
utils::{generics, Generics},
|
utils::{generics, Generics},
|
||||||
AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, Rawness, Scalar,
|
AdtId, AliasEq, AliasTy, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner,
|
||||||
Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
|
ProjectionTy, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -564,9 +564,29 @@ impl<'a> InferenceContext<'a> {
|
||||||
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
|
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
|
||||||
self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
|
self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
|
||||||
}
|
}
|
||||||
Expr::Try { expr } => {
|
&Expr::Try { expr } => {
|
||||||
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
|
let inner_ty = self.infer_expr_inner(expr, &Expectation::none());
|
||||||
self.resolve_associated_type(inner_ty, self.resolve_ops_try_ok())
|
match self.resolve_try_impl_for(inner_ty.clone()) {
|
||||||
|
Some((_, Some((output, residual)))) => {
|
||||||
|
if let Some((_trait, false)) =
|
||||||
|
self.implements_from_residual(self.return_ty.clone(), residual)
|
||||||
|
{
|
||||||
|
self.push_diagnostic(InferenceDiagnostic::IncorrectTryTarget {
|
||||||
|
expr: tgt_expr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
Some((trait_, None)) => {
|
||||||
|
self.push_diagnostic(InferenceDiagnostic::DoesNotImplement {
|
||||||
|
expr,
|
||||||
|
trait_,
|
||||||
|
ty: inner_ty,
|
||||||
|
});
|
||||||
|
self.err_ty()
|
||||||
|
}
|
||||||
|
None => self.err_ty(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::Cast { expr, type_ref } => {
|
Expr::Cast { expr, type_ref } => {
|
||||||
// FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary)
|
// FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary)
|
||||||
|
@ -1530,4 +1550,67 @@ impl<'a> InferenceContext<'a> {
|
||||||
let ctx = self.breakables.pop().expect("breakable stack broken");
|
let ctx = self.breakables.pop().expect("breakable stack broken");
|
||||||
(ctx.may_break.then(|| ctx.coerce.complete()), res)
|
(ctx.may_break.then(|| ctx.coerce.complete()), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether `ty` implements `FromResidual<r>`
|
||||||
|
fn implements_from_residual(&mut self, ty: Ty, r: Ty) -> Option<(hir_def::TraitId, bool)> {
|
||||||
|
let from_residual_trait = self
|
||||||
|
.resolver
|
||||||
|
.resolve_known_trait(self.db.upcast(), &(super::path![core::ops::FromResidual]))?;
|
||||||
|
let r = GenericArgData::Ty(r).intern(Interner);
|
||||||
|
let b = TyBuilder::trait_ref(self.db, from_residual_trait);
|
||||||
|
if b.remaining() != 2 {
|
||||||
|
return Some((from_residual_trait, false));
|
||||||
|
}
|
||||||
|
let trait_ref = b.push(ty).push(r).build();
|
||||||
|
Some((from_residual_trait, self.table.try_obligation(trait_ref.cast(Interner)).is_some()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_try_impl_for(&mut self, ty: Ty) -> Option<(hir_def::TraitId, Option<(Ty, Ty)>)> {
|
||||||
|
let path = path![core::ops::Try];
|
||||||
|
let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
|
||||||
|
|
||||||
|
let trait_ref = TyBuilder::trait_ref(self.db, trait_).push(ty).build();
|
||||||
|
let substitution = trait_ref.substitution.clone();
|
||||||
|
self.push_obligation(trait_ref.clone().cast(Interner));
|
||||||
|
|
||||||
|
let trait_data = self.db.trait_data(trait_);
|
||||||
|
let output = trait_data.associated_type_by_name(&name![Output]);
|
||||||
|
let residual = trait_data.associated_type_by_name(&name![Residual]);
|
||||||
|
|
||||||
|
let output_ty = match output {
|
||||||
|
Some(output) => {
|
||||||
|
let output_ty = self.table.new_type_var();
|
||||||
|
let alias_eq = AliasEq {
|
||||||
|
alias: AliasTy::Projection(ProjectionTy {
|
||||||
|
associated_ty_id: to_assoc_type_id(output),
|
||||||
|
substitution: substitution.clone(),
|
||||||
|
}),
|
||||||
|
ty: output_ty.clone(),
|
||||||
|
};
|
||||||
|
self.push_obligation(alias_eq.cast(Interner));
|
||||||
|
output_ty
|
||||||
|
}
|
||||||
|
None => self.err_ty(),
|
||||||
|
};
|
||||||
|
let residual_ty = match residual {
|
||||||
|
Some(residual) => {
|
||||||
|
let residual_ty = self.table.new_type_var();
|
||||||
|
let alias_eq = AliasEq {
|
||||||
|
alias: AliasTy::Projection(ProjectionTy {
|
||||||
|
associated_ty_id: to_assoc_type_id(residual),
|
||||||
|
substitution,
|
||||||
|
}),
|
||||||
|
ty: residual_ty.clone(),
|
||||||
|
};
|
||||||
|
self.push_obligation(alias_eq.cast(Interner));
|
||||||
|
residual_ty
|
||||||
|
}
|
||||||
|
None => self.err_ty(),
|
||||||
|
};
|
||||||
|
// FIXME: We are doing the work twice here I think?
|
||||||
|
Some((
|
||||||
|
trait_,
|
||||||
|
self.table.try_obligation(trait_ref.cast(Interner)).map(|_| (output_ty, residual_ty)),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1111,6 +1111,24 @@ pub fn resolve_indexing_op(
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
/// Returns the receiver type for the try branch trait call.
|
||||||
|
pub fn resolve_branch_op(
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
env: Arc<TraitEnvironment>,
|
||||||
|
ty: Canonical<Ty>,
|
||||||
|
try_trait: TraitId,
|
||||||
|
) -> Option<ReceiverAdjustments> {
|
||||||
|
let mut table = InferenceTable::new(db, env.clone());
|
||||||
|
let ty = table.instantiate_canonical(ty);
|
||||||
|
let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty);
|
||||||
|
for (ty, adj) in deref_chain.into_iter().zip(adj) {
|
||||||
|
let goal = generic_implements_goal(db, env.clone(), try_trait, &ty);
|
||||||
|
if db.trait_solve(env.krate, goal.cast(Interner)).is_some() {
|
||||||
|
return Some(adj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! check_that {
|
macro_rules! check_that {
|
||||||
($cond:expr) => {
|
($cond:expr) => {
|
||||||
|
|
|
@ -162,98 +162,16 @@ fn test() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn infer_try() {
|
|
||||||
check_types(
|
|
||||||
r#"
|
|
||||||
//- /main.rs crate:main deps:core
|
|
||||||
fn test() {
|
|
||||||
let r: Result<i32, u64> = Result::Ok(1);
|
|
||||||
let v = r?;
|
|
||||||
v;
|
|
||||||
} //^ i32
|
|
||||||
|
|
||||||
//- /core.rs crate:core
|
|
||||||
pub mod ops {
|
|
||||||
pub trait Try {
|
|
||||||
type Ok;
|
|
||||||
type Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod result {
|
|
||||||
pub enum Result<O, E> {
|
|
||||||
Ok(O),
|
|
||||||
Err(E)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<O, E> crate::ops::Try for Result<O, E> {
|
|
||||||
type Ok = O;
|
|
||||||
type Error = E;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod prelude {
|
|
||||||
pub mod rust_2018 {
|
|
||||||
pub use crate::{result::*, ops::*};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn infer_try_trait_v2() {
|
fn infer_try_trait_v2() {
|
||||||
check_types(
|
check_types(
|
||||||
r#"
|
r#"
|
||||||
//- /main.rs crate:main deps:core
|
//- minicore: try
|
||||||
fn test() {
|
fn test() -> core::ops::ControlFlow<u32, f32> {
|
||||||
let r: Result<i32, u64> = Result::Ok(1);
|
let r: core::ops::ControlFlow<u32, f32> = core::ops::ControlFlow::Continue(1.0);
|
||||||
let v = r?;
|
let v = r?;
|
||||||
v;
|
//^ f32
|
||||||
} //^ i32
|
r
|
||||||
|
|
||||||
//- /core.rs crate:core
|
|
||||||
mod ops {
|
|
||||||
mod try_trait {
|
|
||||||
pub trait Try: FromResidual {
|
|
||||||
type Output;
|
|
||||||
type Residual;
|
|
||||||
}
|
|
||||||
pub trait FromResidual<R = <Self as Try>::Residual> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use self::try_trait::FromResidual;
|
|
||||||
pub use self::try_trait::Try;
|
|
||||||
}
|
|
||||||
|
|
||||||
mod convert {
|
|
||||||
pub trait From<T> {}
|
|
||||||
impl<T> From<T> for T {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod result {
|
|
||||||
use crate::convert::From;
|
|
||||||
use crate::ops::{Try, FromResidual};
|
|
||||||
|
|
||||||
pub enum Infallible {}
|
|
||||||
pub enum Result<O, E> {
|
|
||||||
Ok(O),
|
|
||||||
Err(E)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<O, E> Try for Result<O, E> {
|
|
||||||
type Output = O;
|
|
||||||
type Error = Result<Infallible, E>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E, F: From<E>> FromResidual<Result<Infallible, E>> for Result<T, F> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod prelude {
|
|
||||||
pub mod rust_2018 {
|
|
||||||
pub use crate::result::*;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
use base_db::CrateId;
|
use base_db::CrateId;
|
||||||
use cfg::{CfgExpr, CfgOptions};
|
use cfg::{CfgExpr, CfgOptions};
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use hir_def::path::ModPath;
|
use hir_def::{path::ModPath, TraitId};
|
||||||
use hir_expand::{name::Name, HirFileId, InFile};
|
use hir_expand::{name::Name, HirFileId, InFile};
|
||||||
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
|
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ diagnostics![
|
||||||
BreakOutsideOfLoop,
|
BreakOutsideOfLoop,
|
||||||
InactiveCode,
|
InactiveCode,
|
||||||
IncorrectCase,
|
IncorrectCase,
|
||||||
|
IncorrectTryExpr,
|
||||||
InvalidDeriveTarget,
|
InvalidDeriveTarget,
|
||||||
MacroError,
|
MacroError,
|
||||||
MalformedDerive,
|
MalformedDerive,
|
||||||
|
@ -40,6 +41,7 @@ diagnostics![
|
||||||
MissingFields,
|
MissingFields,
|
||||||
MissingMatchArms,
|
MissingMatchArms,
|
||||||
MissingUnsafe,
|
MissingUnsafe,
|
||||||
|
NotImplemented,
|
||||||
NoSuchField,
|
NoSuchField,
|
||||||
ReplaceFilterMapNextWithFindMap,
|
ReplaceFilterMapNextWithFindMap,
|
||||||
TypeMismatch,
|
TypeMismatch,
|
||||||
|
@ -153,6 +155,16 @@ pub struct MismatchedArgCount {
|
||||||
pub expected: usize,
|
pub expected: usize,
|
||||||
pub found: usize,
|
pub found: usize,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IncorrectTryExpr {
|
||||||
|
pub expr: InFile<AstPtr<ast::Expr>>,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NotImplemented {
|
||||||
|
pub expr: InFile<AstPtr<ast::Expr>>,
|
||||||
|
pub trait_: TraitId,
|
||||||
|
pub ty: Type,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MissingMatchArms {
|
pub struct MissingMatchArms {
|
||||||
|
|
|
@ -81,11 +81,12 @@ use crate::db::{DefDatabase, HirDatabase};
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
attrs::{HasAttrs, Namespace},
|
attrs::{HasAttrs, Namespace},
|
||||||
diagnostics::{
|
diagnostics::{
|
||||||
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
|
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, IncorrectTryExpr,
|
||||||
MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
|
InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
|
||||||
MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch,
|
MissingMatchArms, MissingUnsafe, NoSuchField, NotImplemented,
|
||||||
UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
|
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
|
||||||
UnresolvedModule, UnresolvedProcMacro,
|
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
|
||||||
|
UnresolvedProcMacro,
|
||||||
},
|
},
|
||||||
has_source::HasSource,
|
has_source::HasSource,
|
||||||
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
|
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
|
||||||
|
@ -1282,30 +1283,45 @@ impl DefWithBody {
|
||||||
let infer = db.infer(self.into());
|
let infer = db.infer(self.into());
|
||||||
let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1);
|
let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1);
|
||||||
for d in &infer.diagnostics {
|
for d in &infer.diagnostics {
|
||||||
match d {
|
match *d {
|
||||||
hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
|
hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
|
||||||
let field = source_map.field_syntax(*expr);
|
let field = source_map.field_syntax(expr);
|
||||||
acc.push(NoSuchField { field }.into())
|
acc.push(NoSuchField { field }.into())
|
||||||
}
|
}
|
||||||
&hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
|
hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
|
||||||
let expr = source_map
|
let expr = source_map
|
||||||
.expr_syntax(expr)
|
.expr_syntax(expr)
|
||||||
.expect("break outside of loop in synthetic syntax");
|
.expect("break outside of loop in synthetic syntax");
|
||||||
acc.push(BreakOutsideOfLoop { expr, is_break }.into())
|
acc.push(BreakOutsideOfLoop { expr, is_break }.into())
|
||||||
}
|
}
|
||||||
hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
|
hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
|
||||||
match source_map.expr_syntax(*call_expr) {
|
match source_map.expr_syntax(call_expr) {
|
||||||
Ok(source_ptr) => acc.push(
|
Ok(source_ptr) => acc.push(
|
||||||
MismatchedArgCount {
|
MismatchedArgCount {
|
||||||
call_expr: source_ptr,
|
call_expr: source_ptr,
|
||||||
expected: *expected,
|
expected: expected,
|
||||||
found: *found,
|
found: found,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
Err(SyntheticSyntax) => (),
|
Err(SyntheticSyntax) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hir_ty::InferenceDiagnostic::IncorrectTryTarget { expr } => {
|
||||||
|
let expr = source_map.expr_syntax(expr).expect("try in synthetic syntax");
|
||||||
|
acc.push(IncorrectTryExpr { expr }.into())
|
||||||
|
}
|
||||||
|
hir_ty::InferenceDiagnostic::DoesNotImplement { expr, trait_, ref ty } => {
|
||||||
|
let expr = source_map.expr_syntax(expr).expect("try in synthetic syntax");
|
||||||
|
acc.push(
|
||||||
|
NotImplemented {
|
||||||
|
expr,
|
||||||
|
trait_,
|
||||||
|
ty: Type::new(db, DefWithBodyId::from(self), ty.clone()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (expr, mismatch) in infer.expr_type_mismatches() {
|
for (expr, mismatch) in infer.expr_type_mismatches() {
|
||||||
|
|
37
crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs
Normal file
37
crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use hir::InFile;
|
||||||
|
|
||||||
|
use crate::{Diagnostic, DiagnosticsContext};
|
||||||
|
|
||||||
|
// Diagnostic: incorrect-try-target
|
||||||
|
//
|
||||||
|
// This diagnostic is triggered if a question mark operator was used in a context where it is not applicable.
|
||||||
|
pub(crate) fn incorrect_try_expr(
|
||||||
|
ctx: &DiagnosticsContext<'_>,
|
||||||
|
d: &hir::IncorrectTryExpr,
|
||||||
|
) -> Diagnostic {
|
||||||
|
Diagnostic::new(
|
||||||
|
"incorrect-try-target",
|
||||||
|
format!("the return type of the containing function does not implement `FromResidual`"),
|
||||||
|
ctx.sema
|
||||||
|
.diagnostics_display_range(InFile::new(d.expr.file_id, d.expr.value.clone().into()))
|
||||||
|
.range,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::tests::check_diagnostics;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn try_ops_diag() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
//- minicore: try
|
||||||
|
fn test() {
|
||||||
|
core::ops::ControlFlow::<u32, f32>::Continue(1.0)?;
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: the return type of the containing function does not implement `FromResidual`
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
crates/ide-diagnostics/src/handlers/not_implemented.rs
Normal file
35
crates/ide-diagnostics/src/handlers/not_implemented.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use hir::{db::DefDatabase, HirDisplay};
|
||||||
|
|
||||||
|
use crate::{Diagnostic, DiagnosticsContext};
|
||||||
|
|
||||||
|
// Diagnostic: not-implemented
|
||||||
|
//
|
||||||
|
// This diagnostic is triggered if a type doesn't implement a necessary trait.
|
||||||
|
pub(crate) fn not_implemented(ctx: &DiagnosticsContext<'_>, d: &hir::NotImplemented) -> Diagnostic {
|
||||||
|
Diagnostic::new(
|
||||||
|
"not-implemented",
|
||||||
|
format!(
|
||||||
|
"the trait `{}` is not implemented for `{}`",
|
||||||
|
ctx.sema.db.trait_data(d.trait_).name,
|
||||||
|
d.ty.display(ctx.sema.db)
|
||||||
|
),
|
||||||
|
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::tests::check_diagnostics;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_try_impl() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
//- minicore: try
|
||||||
|
fn main() {
|
||||||
|
()?;
|
||||||
|
} //^^ error: the trait `Try` is not implemented for `()`
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ mod handlers {
|
||||||
pub(crate) mod break_outside_of_loop;
|
pub(crate) mod break_outside_of_loop;
|
||||||
pub(crate) mod inactive_code;
|
pub(crate) mod inactive_code;
|
||||||
pub(crate) mod incorrect_case;
|
pub(crate) mod incorrect_case;
|
||||||
|
pub(crate) mod incorrect_try_expr;
|
||||||
pub(crate) mod invalid_derive_target;
|
pub(crate) mod invalid_derive_target;
|
||||||
pub(crate) mod macro_error;
|
pub(crate) mod macro_error;
|
||||||
pub(crate) mod malformed_derive;
|
pub(crate) mod malformed_derive;
|
||||||
|
@ -36,6 +37,7 @@ mod handlers {
|
||||||
pub(crate) mod missing_fields;
|
pub(crate) mod missing_fields;
|
||||||
pub(crate) mod missing_match_arms;
|
pub(crate) mod missing_match_arms;
|
||||||
pub(crate) mod missing_unsafe;
|
pub(crate) mod missing_unsafe;
|
||||||
|
pub(crate) mod not_implemented;
|
||||||
pub(crate) mod no_such_field;
|
pub(crate) mod no_such_field;
|
||||||
pub(crate) mod replace_filter_map_next_with_find_map;
|
pub(crate) mod replace_filter_map_next_with_find_map;
|
||||||
pub(crate) mod type_mismatch;
|
pub(crate) mod type_mismatch;
|
||||||
|
@ -225,12 +227,14 @@ pub fn diagnostics(
|
||||||
let d = match diag {
|
let d = match diag {
|
||||||
AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&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::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
|
||||||
|
AnyDiagnostic::IncorrectTryExpr(d) => handlers::incorrect_try_expr::incorrect_try_expr(&ctx, &d),
|
||||||
AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
|
AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
|
||||||
AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),
|
AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),
|
||||||
AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d),
|
AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d),
|
||||||
AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&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::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d),
|
||||||
AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
|
AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
|
||||||
|
AnyDiagnostic::NotImplemented(d) => handlers::not_implemented::not_implemented(&ctx, &d),
|
||||||
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
|
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
|
||||||
AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&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::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),
|
||||||
|
|
|
@ -4913,6 +4913,22 @@ fn foo() -> NotResult<(), Short> {
|
||||||
```
|
```
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
//- minicore: try
|
||||||
|
use core::ops::ControlFlow;
|
||||||
|
fn foo() -> ControlFlow<()> {
|
||||||
|
$0ControlFlow::Break(())?$0;
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```text
|
||||||
|
Try Target Type: ControlFlow<(), {unknown}>
|
||||||
|
Propagated as: ControlFlow<(), ()>
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -4929,7 +4945,7 @@ fn foo() -> Option<()> {
|
||||||
"#,
|
"#,
|
||||||
expect![[r#"
|
expect![[r#"
|
||||||
```rust
|
```rust
|
||||||
<Option<i32> as Try>::Output
|
i32
|
||||||
```"#]],
|
```"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
//! generator: pin
|
//! generator: pin
|
||||||
//! hash:
|
//! hash:
|
||||||
//! index: sized
|
//! index: sized
|
||||||
|
//! infallible:
|
||||||
//! iterator: option
|
//! iterator: option
|
||||||
//! iterators: iterator, fn
|
//! iterators: iterator, fn
|
||||||
//! option:
|
//! option:
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
//! result:
|
//! result:
|
||||||
//! sized:
|
//! sized:
|
||||||
//! slice:
|
//! slice:
|
||||||
//! try:
|
//! try: infallible
|
||||||
//! unsize: sized
|
//! unsize: sized
|
||||||
|
|
||||||
pub mod marker {
|
pub mod marker {
|
||||||
|
@ -150,6 +151,9 @@ pub mod convert {
|
||||||
fn as_ref(&self) -> &T;
|
fn as_ref(&self) -> &T;
|
||||||
}
|
}
|
||||||
// endregion:as_ref
|
// endregion:as_ref
|
||||||
|
// region:infallible
|
||||||
|
pub enum Infallible {}
|
||||||
|
// endregion:infallible
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod ops {
|
pub mod ops {
|
||||||
|
@ -326,7 +330,7 @@ pub mod ops {
|
||||||
Continue(C),
|
Continue(C),
|
||||||
Break(B),
|
Break(B),
|
||||||
}
|
}
|
||||||
pub trait FromResidual<R = Self::Residual> {
|
pub trait FromResidual<R = <Self as Try>::Residual> {
|
||||||
#[lang = "from_residual"]
|
#[lang = "from_residual"]
|
||||||
fn from_residual(residual: R) -> Self;
|
fn from_residual(residual: R) -> Self;
|
||||||
}
|
}
|
||||||
|
@ -342,13 +346,13 @@ pub mod ops {
|
||||||
|
|
||||||
impl<B, C> Try for ControlFlow<B, C> {
|
impl<B, C> Try for ControlFlow<B, C> {
|
||||||
type Output = C;
|
type Output = C;
|
||||||
type Residual = ControlFlow<B, convert::Infallible>;
|
type Residual = ControlFlow<B, crate::convert::Infallible>;
|
||||||
fn from_output(output: Self::Output) -> Self {}
|
fn from_output(output: Self::Output) -> Self {}
|
||||||
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {}
|
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, C> FromResidual for ControlFlow<B, C> {
|
impl<B, C> FromResidual for ControlFlow<B, C> {
|
||||||
fn from_residual(residual: ControlFlow<B, convert::Infallible>) -> Self {}
|
fn from_residual(residual: ControlFlow<B, crate::convert::Infallible>) -> Self {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub use self::try_::{ControlFlow, FromResidual, Try};
|
pub use self::try_::{ControlFlow, FromResidual, Try};
|
||||||
|
@ -469,6 +473,33 @@ pub mod option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// region:try
|
||||||
|
impl<T> crate::ops::Try for Option<T> {
|
||||||
|
type Output = T;
|
||||||
|
type Residual = Option<crate::convert::Infallible>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_output(output: Self::Output) -> Self {
|
||||||
|
Some(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn branch(self) -> crate::ops::ControlFlow<Self::Residual, Self::Output> {
|
||||||
|
match self {
|
||||||
|
Some(v) => crate::ops::ControlFlow::Continue(v),
|
||||||
|
None => crate::ops::ControlFlow::Break(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> crate::ops::FromResidual for Option<T> {
|
||||||
|
#[inline]
|
||||||
|
fn from_residual(residual: Option<crate::convert::Infallible>) -> Self {
|
||||||
|
match residual {
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// endregion:try
|
||||||
}
|
}
|
||||||
// endregion:option
|
// endregion:option
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue