Skip to content

Commit ee2d9ed

Browse files
committedOct 16, 2022
Auto merge of rust-lang#13354 - Veykril:try-stuff, r=Veykril
feat: Diagnose some incorrect usages of the question mark operator Trying to figure out how the type stuff in r-a works some more, I think I am doing this correct here but I am not quite sure :)
2 parents 4876693 + 381366f commit ee2d9ed

File tree

13 files changed

+289
-125
lines changed

13 files changed

+289
-125
lines changed
 

Diff for: ‎crates/hir-expand/src/mod_path.rs

+1
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ macro_rules! __known_path {
259259
(core::future::Future) => {};
260260
(core::future::IntoFuture) => {};
261261
(core::ops::Try) => {};
262+
(core::ops::FromResidual) => {};
262263
($path:path) => {
263264
compile_error!("Please register your known path in the path module")
264265
};

Diff for: ‎crates/hir-expand/src/name.rs

+2
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ pub mod known {
279279
RangeToInclusive,
280280
RangeTo,
281281
Range,
282+
Residual,
283+
FromResidual,
282284
Neg,
283285
Not,
284286
None,

Diff for: ‎crates/hir-ty/src/infer.rs

+2-11
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
190190
pub enum InferenceDiagnostic {
191191
NoSuchField { expr: ExprId },
192192
BreakOutsideOfLoop { expr: ExprId, is_break: bool },
193+
IncorrectTryTarget { expr: ExprId },
193194
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
195+
DoesNotImplement { expr: ExprId, trait_: TraitId, ty: Ty },
194196
}
195197

196198
/// A mismatch between an expected and an inferred type.
@@ -905,17 +907,6 @@ impl<'a> InferenceContext<'a> {
905907
self.db.trait_data(trait_).associated_type_by_name(&name![Item])
906908
}
907909

908-
fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
909-
// FIXME resolve via lang_item once try v2 is stable
910-
let path = path![core::ops::Try];
911-
let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
912-
let trait_data = self.db.trait_data(trait_);
913-
trait_data
914-
// FIXME remove once try v2 is stable
915-
.associated_type_by_name(&name![Ok])
916-
.or_else(|| trait_data.associated_type_by_name(&name![Output]))
917-
}
918-
919910
fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
920911
let trait_ = self.resolve_lang_item(name![neg])?.as_trait()?;
921912
self.db.trait_data(trait_).associated_type_by_name(&name![Output])

Diff for: ‎crates/hir-ty/src/infer/expr.rs

+91-8
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@ use hir_def::{
1919
resolver::resolver_for_expr,
2020
ConstParamId, FieldId, ItemContainerId, Lookup,
2121
};
22-
use hir_expand::name::Name;
22+
use hir_expand::{name, name::Name};
2323
use stdx::always;
2424
use syntax::ast::RangeOp;
2525

2626
use crate::{
2727
autoderef::{self, Autoderef},
2828
consteval,
29-
infer::{coerce::CoerceMany, find_continuable, BreakableKind},
29+
infer::{coerce::CoerceMany, find_continuable, path, BreakableKind},
3030
lower::{
3131
const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
3232
},
3333
mapping::{from_chalk, ToChalk},
3434
method_resolution::{self, lang_names_for_bin_op, VisibleFromModule},
3535
primitive::{self, UintTy},
36-
static_lifetime, to_chalk_trait_id,
36+
static_lifetime, to_assoc_type_id, to_chalk_trait_id,
3737
utils::{generics, Generics},
38-
AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, Rawness, Scalar,
39-
Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
38+
AdtId, AliasEq, AliasTy, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner,
39+
ProjectionTy, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
4040
};
4141

4242
use super::{
@@ -564,9 +564,29 @@ impl<'a> InferenceContext<'a> {
564564
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
565565
self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
566566
}
567-
Expr::Try { expr } => {
568-
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
569-
self.resolve_associated_type(inner_ty, self.resolve_ops_try_ok())
567+
&Expr::Try { expr } => {
568+
let inner_ty = self.infer_expr_inner(expr, &Expectation::none());
569+
match self.resolve_try_impl_for(inner_ty.clone()) {
570+
Some((_, Some((output, residual)))) => {
571+
if let Some((_trait, false)) =
572+
self.implements_from_residual(self.return_ty.clone(), residual)
573+
{
574+
self.push_diagnostic(InferenceDiagnostic::IncorrectTryTarget {
575+
expr: tgt_expr,
576+
});
577+
}
578+
output
579+
}
580+
Some((trait_, None)) => {
581+
self.push_diagnostic(InferenceDiagnostic::DoesNotImplement {
582+
expr,
583+
trait_,
584+
ty: inner_ty,
585+
});
586+
self.err_ty()
587+
}
588+
None => self.err_ty(),
589+
}
570590
}
571591
Expr::Cast { expr, type_ref } => {
572592
// FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary)
@@ -1530,4 +1550,67 @@ impl<'a> InferenceContext<'a> {
15301550
let ctx = self.breakables.pop().expect("breakable stack broken");
15311551
(ctx.may_break.then(|| ctx.coerce.complete()), res)
15321552
}
1553+
1554+
/// Check whether `ty` implements `FromResidual<r>`
1555+
fn implements_from_residual(&mut self, ty: Ty, r: Ty) -> Option<(hir_def::TraitId, bool)> {
1556+
let from_residual_trait = self
1557+
.resolver
1558+
.resolve_known_trait(self.db.upcast(), &(super::path![core::ops::FromResidual]))?;
1559+
let r = GenericArgData::Ty(r).intern(Interner);
1560+
let b = TyBuilder::trait_ref(self.db, from_residual_trait);
1561+
if b.remaining() != 2 {
1562+
return Some((from_residual_trait, false));
1563+
}
1564+
let trait_ref = b.push(ty).push(r).build();
1565+
Some((from_residual_trait, self.table.try_obligation(trait_ref.cast(Interner)).is_some()))
1566+
}
1567+
1568+
fn resolve_try_impl_for(&mut self, ty: Ty) -> Option<(hir_def::TraitId, Option<(Ty, Ty)>)> {
1569+
let path = path![core::ops::Try];
1570+
let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
1571+
1572+
let trait_ref = TyBuilder::trait_ref(self.db, trait_).push(ty).build();
1573+
let substitution = trait_ref.substitution.clone();
1574+
self.push_obligation(trait_ref.clone().cast(Interner));
1575+
1576+
let trait_data = self.db.trait_data(trait_);
1577+
let output = trait_data.associated_type_by_name(&name![Output]);
1578+
let residual = trait_data.associated_type_by_name(&name![Residual]);
1579+
1580+
let output_ty = match output {
1581+
Some(output) => {
1582+
let output_ty = self.table.new_type_var();
1583+
let alias_eq = AliasEq {
1584+
alias: AliasTy::Projection(ProjectionTy {
1585+
associated_ty_id: to_assoc_type_id(output),
1586+
substitution: substitution.clone(),
1587+
}),
1588+
ty: output_ty.clone(),
1589+
};
1590+
self.push_obligation(alias_eq.cast(Interner));
1591+
output_ty
1592+
}
1593+
None => self.err_ty(),
1594+
};
1595+
let residual_ty = match residual {
1596+
Some(residual) => {
1597+
let residual_ty = self.table.new_type_var();
1598+
let alias_eq = AliasEq {
1599+
alias: AliasTy::Projection(ProjectionTy {
1600+
associated_ty_id: to_assoc_type_id(residual),
1601+
substitution,
1602+
}),
1603+
ty: residual_ty.clone(),
1604+
};
1605+
self.push_obligation(alias_eq.cast(Interner));
1606+
residual_ty
1607+
}
1608+
None => self.err_ty(),
1609+
};
1610+
// FIXME: We are doing the work twice here I think?
1611+
Some((
1612+
trait_,
1613+
self.table.try_obligation(trait_ref.cast(Interner)).map(|_| (output_ty, residual_ty)),
1614+
))
1615+
}
15331616
}

Diff for: ‎crates/hir-ty/src/method_resolution.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,24 @@ pub fn resolve_indexing_op(
11111111
}
11121112
None
11131113
}
1114+
/// Returns the receiver type for the try branch trait call.
1115+
pub fn resolve_branch_op(
1116+
db: &dyn HirDatabase,
1117+
env: Arc<TraitEnvironment>,
1118+
ty: Canonical<Ty>,
1119+
try_trait: TraitId,
1120+
) -> Option<ReceiverAdjustments> {
1121+
let mut table = InferenceTable::new(db, env.clone());
1122+
let ty = table.instantiate_canonical(ty);
1123+
let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty);
1124+
for (ty, adj) in deref_chain.into_iter().zip(adj) {
1125+
let goal = generic_implements_goal(db, env.clone(), try_trait, &ty);
1126+
if db.trait_solve(env.krate, goal.cast(Interner)).is_some() {
1127+
return Some(adj);
1128+
}
1129+
}
1130+
None
1131+
}
11141132

11151133
macro_rules! check_that {
11161134
($cond:expr) => {

Diff for: ‎crates/hir-ty/src/tests/traits.rs

+5-87
Original file line numberDiff line numberDiff line change
@@ -162,98 +162,16 @@ fn test() {
162162
);
163163
}
164164

165-
#[test]
166-
fn infer_try() {
167-
check_types(
168-
r#"
169-
//- /main.rs crate:main deps:core
170-
fn test() {
171-
let r: Result<i32, u64> = Result::Ok(1);
172-
let v = r?;
173-
v;
174-
} //^ i32
175-
176-
//- /core.rs crate:core
177-
pub mod ops {
178-
pub trait Try {
179-
type Ok;
180-
type Error;
181-
}
182-
}
183-
184-
pub mod result {
185-
pub enum Result<O, E> {
186-
Ok(O),
187-
Err(E)
188-
}
189-
190-
impl<O, E> crate::ops::Try for Result<O, E> {
191-
type Ok = O;
192-
type Error = E;
193-
}
194-
}
195-
196-
pub mod prelude {
197-
pub mod rust_2018 {
198-
pub use crate::{result::*, ops::*};
199-
}
200-
}
201-
"#,
202-
);
203-
}
204-
205165
#[test]
206166
fn infer_try_trait_v2() {
207167
check_types(
208168
r#"
209-
//- /main.rs crate:main deps:core
210-
fn test() {
211-
let r: Result<i32, u64> = Result::Ok(1);
169+
//- minicore: try
170+
fn test() -> core::ops::ControlFlow<u32, f32> {
171+
let r: core::ops::ControlFlow<u32, f32> = core::ops::ControlFlow::Continue(1.0);
212172
let v = r?;
213-
v;
214-
} //^ i32
215-
216-
//- /core.rs crate:core
217-
mod ops {
218-
mod try_trait {
219-
pub trait Try: FromResidual {
220-
type Output;
221-
type Residual;
222-
}
223-
pub trait FromResidual<R = <Self as Try>::Residual> {}
224-
}
225-
226-
pub use self::try_trait::FromResidual;
227-
pub use self::try_trait::Try;
228-
}
229-
230-
mod convert {
231-
pub trait From<T> {}
232-
impl<T> From<T> for T {}
233-
}
234-
235-
pub mod result {
236-
use crate::convert::From;
237-
use crate::ops::{Try, FromResidual};
238-
239-
pub enum Infallible {}
240-
pub enum Result<O, E> {
241-
Ok(O),
242-
Err(E)
243-
}
244-
245-
impl<O, E> Try for Result<O, E> {
246-
type Output = O;
247-
type Error = Result<Infallible, E>;
248-
}
249-
250-
impl<T, E, F: From<E>> FromResidual<Result<Infallible, E>> for Result<T, F> {}
251-
}
252-
253-
pub mod prelude {
254-
pub mod rust_2018 {
255-
pub use crate::result::*;
256-
}
173+
//^ f32
174+
r
257175
}
258176
"#,
259177
);

Diff for: ‎crates/hir/src/diagnostics.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use base_db::CrateId;
77
use cfg::{CfgExpr, CfgOptions};
88
use either::Either;
9-
use hir_def::path::ModPath;
9+
use hir_def::{path::ModPath, TraitId};
1010
use hir_expand::{name::Name, HirFileId, InFile};
1111
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
1212

@@ -33,13 +33,15 @@ diagnostics![
3333
BreakOutsideOfLoop,
3434
InactiveCode,
3535
IncorrectCase,
36+
IncorrectTryExpr,
3637
InvalidDeriveTarget,
3738
MacroError,
3839
MalformedDerive,
3940
MismatchedArgCount,
4041
MissingFields,
4142
MissingMatchArms,
4243
MissingUnsafe,
44+
NotImplemented,
4345
NoSuchField,
4446
ReplaceFilterMapNextWithFindMap,
4547
TypeMismatch,
@@ -153,6 +155,16 @@ pub struct MismatchedArgCount {
153155
pub expected: usize,
154156
pub found: usize,
155157
}
158+
#[derive(Debug)]
159+
pub struct IncorrectTryExpr {
160+
pub expr: InFile<AstPtr<ast::Expr>>,
161+
}
162+
#[derive(Debug)]
163+
pub struct NotImplemented {
164+
pub expr: InFile<AstPtr<ast::Expr>>,
165+
pub trait_: TraitId,
166+
pub ty: Type,
167+
}
156168

157169
#[derive(Debug)]
158170
pub struct MissingMatchArms {

0 commit comments

Comments
 (0)
Please sign in to comment.