(core::future::Future) => {};
(core::future::IntoFuture) => {};
(core::ops::Try) => {};
+ (core::ops::FromResidual) => {};
($path:path) => {
compile_error!("Please register your known path in the path module")
};
RangeToInclusive,
RangeTo,
Range,
+ Residual,
+ FromResidual,
Neg,
Not,
None,
pub enum InferenceDiagnostic {
NoSuchField { expr: ExprId },
BreakOutsideOfLoop { expr: ExprId, is_break: bool },
+ IncorrectTryTarget { expr: ExprId },
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
+ DoesNotImplement { expr: ExprId, trait_: TraitId, ty: Ty },
}
/// A mismatch between an expected and an inferred type.
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> {
let trait_ = self.resolve_lang_item(name![neg])?.as_trait()?;
self.db.trait_data(trait_).associated_type_by_name(&name![Output])
resolver::resolver_for_expr,
ConstParamId, FieldId, ItemContainerId, Lookup,
};
-use hir_expand::name::Name;
+use hir_expand::{name, name::Name};
use stdx::always;
use syntax::ast::RangeOp;
use crate::{
autoderef::{self, Autoderef},
consteval,
- infer::{coerce::CoerceMany, find_continuable, BreakableKind},
+ infer::{coerce::CoerceMany, find_continuable, path, BreakableKind},
lower::{
const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
},
mapping::{from_chalk, ToChalk},
method_resolution::{self, lang_names_for_bin_op, VisibleFromModule},
primitive::{self, UintTy},
- static_lifetime, to_chalk_trait_id,
+ static_lifetime, to_assoc_type_id, to_chalk_trait_id,
utils::{generics, Generics},
- AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, Rawness, Scalar,
- Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
+ AdtId, AliasEq, AliasTy, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner,
+ ProjectionTy, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
};
use super::{
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
}
- Expr::Try { expr } => {
- let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
- self.resolve_associated_type(inner_ty, self.resolve_ops_try_ok())
+ &Expr::Try { expr } => {
+ let inner_ty = self.infer_expr_inner(expr, &Expectation::none());
+ 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 } => {
// FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary)
let ctx = self.breakables.pop().expect("breakable stack broken");
(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)),
+ ))
+ }
}
}
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 {
($cond:expr) => {
);
}
-#[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]
fn infer_try_trait_v2() {
check_types(
r#"
-//- /main.rs crate:main deps:core
-fn test() {
- let r: Result<i32, u64> = Result::Ok(1);
+//- minicore: try
+fn test() -> core::ops::ControlFlow<u32, f32> {
+ let r: core::ops::ControlFlow<u32, f32> = core::ops::ControlFlow::Continue(1.0);
let v = r?;
- v;
-} //^ i32
-
-//- /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::*;
- }
+ //^ f32
+ r
}
"#,
);
use base_db::CrateId;
use cfg::{CfgExpr, CfgOptions};
use either::Either;
-use hir_def::path::ModPath;
+use hir_def::{path::ModPath, TraitId};
use hir_expand::{name::Name, HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
BreakOutsideOfLoop,
InactiveCode,
IncorrectCase,
+ IncorrectTryExpr,
InvalidDeriveTarget,
MacroError,
MalformedDerive,
MissingFields,
MissingMatchArms,
MissingUnsafe,
+ NotImplemented,
NoSuchField,
ReplaceFilterMapNextWithFindMap,
TypeMismatch,
pub expected: 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)]
pub struct MissingMatchArms {
pub use crate::{
attrs::{HasAttrs, Namespace},
diagnostics::{
- AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
- MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
- MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch,
- UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
- UnresolvedModule, UnresolvedProcMacro,
+ AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, IncorrectTryExpr,
+ InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
+ MissingMatchArms, MissingUnsafe, NoSuchField, NotImplemented,
+ ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
+ UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
+ UnresolvedProcMacro,
},
has_source::HasSource,
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
let infer = db.infer(self.into());
let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1);
for d in &infer.diagnostics {
- match d {
+ match *d {
hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
- let field = source_map.field_syntax(*expr);
+ let field = source_map.field_syntax(expr);
acc.push(NoSuchField { field }.into())
}
- &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
+ hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
let expr = source_map
.expr_syntax(expr)
.expect("break outside of loop in synthetic syntax");
acc.push(BreakOutsideOfLoop { expr, is_break }.into())
}
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(
MismatchedArgCount {
call_expr: source_ptr,
- expected: *expected,
- found: *found,
+ expected: expected,
+ found: found,
}
.into(),
),
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() {
--- /dev/null
+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`
+}
+"#,
+ );
+ }
+}
--- /dev/null
+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 `()`
+"#,
+ )
+ }
+}
pub(crate) mod break_outside_of_loop;
pub(crate) mod inactive_code;
pub(crate) mod incorrect_case;
+ pub(crate) mod incorrect_try_expr;
pub(crate) mod invalid_derive_target;
pub(crate) mod macro_error;
pub(crate) mod malformed_derive;
pub(crate) mod missing_fields;
pub(crate) mod missing_match_arms;
pub(crate) mod missing_unsafe;
+ pub(crate) mod not_implemented;
pub(crate) mod no_such_field;
pub(crate) mod replace_filter_map_next_with_find_map;
pub(crate) mod type_mismatch;
let d = match diag {
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::IncorrectTryExpr(d) => handlers::incorrect_try_expr::incorrect_try_expr(&ctx, &d),
AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&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::MissingFields(d) => handlers::missing_fields::missing_fields(&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::NotImplemented(d) => handlers::not_implemented::not_implemented(&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::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),
```
"#]],
);
+ 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]
}
"#,
expect![[r#"
- ```rust
- <Option<i32> as Try>::Output
- ```"#]],
+ ```rust
+ i32
+ ```"#]],
);
}
//! generator: pin
//! hash:
//! index: sized
+//! infallible:
//! iterator: option
//! iterators: iterator, fn
//! option:
//! result:
//! sized:
//! slice:
-//! try:
+//! try: infallible
//! unsize: sized
pub mod marker {
fn as_ref(&self) -> &T;
}
// endregion:as_ref
+ // region:infallible
+ pub enum Infallible {}
+ // endregion:infallible
}
pub mod ops {
Continue(C),
Break(B),
}
- pub trait FromResidual<R = Self::Residual> {
+ pub trait FromResidual<R = <Self as Try>::Residual> {
#[lang = "from_residual"]
fn from_residual(residual: R) -> Self;
}
impl<B, C> Try for ControlFlow<B, 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 branch(self) -> ControlFlow<Self::Residual, Self::Output> {}
}
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};
}
}
}
+ // 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