Skip to content

Commit f9ec7ce

Browse files
Merge #4377
4377: Implement better handling of divergence r=matklad a=flodiebold Divergence here means that for some reason, the end of a block will not be reached. We tried to model this just using the never type, but that doesn't work fully (e.g. in `let x = { loop {}; "foo" };` x should still have type `&str`); so this introduces a `diverges` flag that the type checker keeps track of, like rustc does. We also add some checking for `break`, but no support for break-with-value or labeled breaks yet. Co-authored-by: Florian Diebold <florian.diebold@freiheit.com> Co-authored-by: Florian Diebold <flodiebold@gmail.com>
2 parents f1fa9aa + d0129c4 commit f9ec7ce

File tree

9 files changed

+362
-25
lines changed

9 files changed

+362
-25
lines changed

crates/ra_hir_ty/src/diagnostics.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,31 @@ impl AstDiagnostic for MissingOkInTailExpr {
131131
ast::Expr::cast(node).unwrap()
132132
}
133133
}
134+
135+
#[derive(Debug)]
136+
pub struct BreakOutsideOfLoop {
137+
pub file: HirFileId,
138+
pub expr: AstPtr<ast::Expr>,
139+
}
140+
141+
impl Diagnostic for BreakOutsideOfLoop {
142+
fn message(&self) -> String {
143+
"break outside of loop".to_string()
144+
}
145+
fn source(&self) -> InFile<SyntaxNodePtr> {
146+
InFile { file_id: self.file, value: self.expr.clone().into() }
147+
}
148+
fn as_any(&self) -> &(dyn Any + Send + 'static) {
149+
self
150+
}
151+
}
152+
153+
impl AstDiagnostic for BreakOutsideOfLoop {
154+
type AST = ast::Expr;
155+
156+
fn ast(&self, db: &impl AstDatabase) -> Self::AST {
157+
let root = db.parse_or_expand(self.file).unwrap();
158+
let node = self.source().value.to_node(&root);
159+
ast::Expr::cast(node).unwrap()
160+
}
161+
}

crates/ra_hir_ty/src/infer.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ struct InferenceContext<'a> {
210210
/// closures, but currently this is the only field that will change there,
211211
/// so it doesn't make sense.
212212
return_ty: Ty,
213+
diverges: Diverges,
214+
breakables: Vec<BreakableContext>,
215+
}
216+
217+
#[derive(Clone, Debug)]
218+
struct BreakableContext {
219+
pub may_break: bool,
213220
}
214221

215222
impl<'a> InferenceContext<'a> {
@@ -224,6 +231,8 @@ impl<'a> InferenceContext<'a> {
224231
owner,
225232
body: db.body(owner),
226233
resolver,
234+
diverges: Diverges::Maybe,
235+
breakables: Vec::new(),
227236
}
228237
}
229238

@@ -666,15 +675,57 @@ impl Expectation {
666675
}
667676
}
668677

678+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
679+
enum Diverges {
680+
Maybe,
681+
Always,
682+
}
683+
684+
impl Diverges {
685+
fn is_always(self) -> bool {
686+
self == Diverges::Always
687+
}
688+
}
689+
690+
impl std::ops::BitAnd for Diverges {
691+
type Output = Self;
692+
fn bitand(self, other: Self) -> Self {
693+
std::cmp::min(self, other)
694+
}
695+
}
696+
697+
impl std::ops::BitOr for Diverges {
698+
type Output = Self;
699+
fn bitor(self, other: Self) -> Self {
700+
std::cmp::max(self, other)
701+
}
702+
}
703+
704+
impl std::ops::BitAndAssign for Diverges {
705+
fn bitand_assign(&mut self, other: Self) {
706+
*self = *self & other;
707+
}
708+
}
709+
710+
impl std::ops::BitOrAssign for Diverges {
711+
fn bitor_assign(&mut self, other: Self) {
712+
*self = *self | other;
713+
}
714+
}
715+
669716
mod diagnostics {
670717
use hir_def::{expr::ExprId, FunctionId};
671718
use hir_expand::diagnostics::DiagnosticSink;
672719

673-
use crate::{db::HirDatabase, diagnostics::NoSuchField};
720+
use crate::{
721+
db::HirDatabase,
722+
diagnostics::{BreakOutsideOfLoop, NoSuchField},
723+
};
674724

675725
#[derive(Debug, PartialEq, Eq, Clone)]
676726
pub(super) enum InferenceDiagnostic {
677727
NoSuchField { expr: ExprId, field: usize },
728+
BreakOutsideOfLoop { expr: ExprId },
678729
}
679730

680731
impl InferenceDiagnostic {
@@ -690,6 +741,13 @@ mod diagnostics {
690741
let field = source_map.field_syntax(*expr, *field);
691742
sink.push(NoSuchField { file: field.file_id, field: field.value })
692743
}
744+
InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
745+
let (_, source_map) = db.body_with_source_map(owner.into());
746+
let ptr = source_map
747+
.expr_syntax(*expr)
748+
.expect("break outside of loop in synthetic syntax");
749+
sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value })
750+
}
693751
}
694752
}
695753
}

crates/ra_hir_ty/src/infer/expr.rs

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Type inference for expressions.
22
33
use std::iter::{repeat, repeat_with};
4-
use std::sync::Arc;
4+
use std::{mem, sync::Arc};
55

66
use hir_def::{
77
builtin_type::Signedness,
@@ -21,11 +21,18 @@ use crate::{
2121
Ty, TypeCtor, Uncertain,
2222
};
2323

24-
use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch};
24+
use super::{
25+
BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic,
26+
TypeMismatch,
27+
};
2528

2629
impl<'a> InferenceContext<'a> {
2730
pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
2831
let ty = self.infer_expr_inner(tgt_expr, expected);
32+
if ty.is_never() {
33+
// Any expression that produces a value of type `!` must have diverged
34+
self.diverges = Diverges::Always;
35+
}
2936
let could_unify = self.unify(&ty, &expected.ty);
3037
if !could_unify {
3138
self.result.type_mismatches.insert(
@@ -64,11 +71,18 @@ impl<'a> InferenceContext<'a> {
6471
// if let is desugared to match, so this is always simple if
6572
self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
6673

74+
let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
75+
let mut both_arms_diverge = Diverges::Always;
76+
6777
let then_ty = self.infer_expr_inner(*then_branch, &expected);
78+
both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe);
6879
let else_ty = match else_branch {
6980
Some(else_branch) => self.infer_expr_inner(*else_branch, &expected),
7081
None => Ty::unit(),
7182
};
83+
both_arms_diverge &= self.diverges;
84+
85+
self.diverges = condition_diverges | both_arms_diverge;
7286

7387
self.coerce_merge_branch(&then_ty, &else_ty)
7488
}
@@ -79,24 +93,43 @@ impl<'a> InferenceContext<'a> {
7993
Ty::Unknown
8094
}
8195
Expr::Loop { body } => {
96+
self.breakables.push(BreakableContext { may_break: false });
8297
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
98+
99+
let ctxt = self.breakables.pop().expect("breakable stack broken");
100+
if ctxt.may_break {
101+
self.diverges = Diverges::Maybe;
102+
}
83103
// FIXME handle break with value
84-
Ty::simple(TypeCtor::Never)
104+
if ctxt.may_break {
105+
Ty::unit()
106+
} else {
107+
Ty::simple(TypeCtor::Never)
108+
}
85109
}
86110
Expr::While { condition, body } => {
111+
self.breakables.push(BreakableContext { may_break: false });
87112
// while let is desugared to a match loop, so this is always simple while
88113
self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
89114
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
115+
let _ctxt = self.breakables.pop().expect("breakable stack broken");
116+
// the body may not run, so it diverging doesn't mean we diverge
117+
self.diverges = Diverges::Maybe;
90118
Ty::unit()
91119
}
92120
Expr::For { iterable, body, pat } => {
93121
let iterable_ty = self.infer_expr(*iterable, &Expectation::none());
94122

123+
self.breakables.push(BreakableContext { may_break: false });
95124
let pat_ty =
96125
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
97126

98127
self.infer_pat(*pat, &pat_ty, BindingMode::default());
128+
99129
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
130+
let _ctxt = self.breakables.pop().expect("breakable stack broken");
131+
// the body may not run, so it diverging doesn't mean we diverge
132+
self.diverges = Diverges::Maybe;
100133
Ty::unit()
101134
}
102135
Expr::Lambda { body, args, ret_type, arg_types } => {
@@ -132,10 +165,12 @@ impl<'a> InferenceContext<'a> {
132165
// infer the body.
133166
self.coerce(&closure_ty, &expected.ty);
134167

135-
let prev_ret_ty = std::mem::replace(&mut self.return_ty, ret_ty.clone());
168+
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
169+
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
136170

137171
self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
138172

173+
self.diverges = prev_diverges;
139174
self.return_ty = prev_ret_ty;
140175

141176
closure_ty
@@ -165,7 +200,11 @@ impl<'a> InferenceContext<'a> {
165200
self.table.new_type_var()
166201
};
167202

203+
let matchee_diverges = self.diverges;
204+
let mut all_arms_diverge = Diverges::Always;
205+
168206
for arm in arms {
207+
self.diverges = Diverges::Maybe;
169208
let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default());
170209
if let Some(guard_expr) = arm.guard {
171210
self.infer_expr(
@@ -175,9 +214,12 @@ impl<'a> InferenceContext<'a> {
175214
}
176215

177216
let arm_ty = self.infer_expr_inner(arm.expr, &expected);
217+
all_arms_diverge &= self.diverges;
178218
result_ty = self.coerce_merge_branch(&result_ty, &arm_ty);
179219
}
180220

221+
self.diverges = matchee_diverges | all_arms_diverge;
222+
181223
result_ty
182224
}
183225
Expr::Path(p) => {
@@ -191,6 +233,13 @@ impl<'a> InferenceContext<'a> {
191233
// FIXME handle break with value
192234
self.infer_expr(*expr, &Expectation::none());
193235
}
236+
if let Some(ctxt) = self.breakables.last_mut() {
237+
ctxt.may_break = true;
238+
} else {
239+
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
240+
expr: tgt_expr,
241+
});
242+
}
194243
Ty::simple(TypeCtor::Never)
195244
}
196245
Expr::Return { expr } => {
@@ -522,7 +571,6 @@ impl<'a> InferenceContext<'a> {
522571
tail: Option<ExprId>,
523572
expected: &Expectation,
524573
) -> Ty {
525-
let mut diverges = false;
526574
for stmt in statements {
527575
match stmt {
528576
Statement::Let { pat, type_ref, initializer } => {
@@ -544,24 +592,30 @@ impl<'a> InferenceContext<'a> {
544592
self.infer_pat(*pat, &ty, BindingMode::default());
545593
}
546594
Statement::Expr(expr) => {
547-
if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) {
548-
diverges = true;
549-
}
595+
self.infer_expr(*expr, &Expectation::none());
550596
}
551597
}
552598
}
553599

554600
let ty = if let Some(expr) = tail {
555601
self.infer_expr_coerce(expr, expected)
556602
} else {
557-
self.coerce(&Ty::unit(), expected.coercion_target());
558-
Ty::unit()
603+
// Citing rustc: if there is no explicit tail expression,
604+
// that is typically equivalent to a tail expression
605+
// of `()` -- except if the block diverges. In that
606+
// case, there is no value supplied from the tail
607+
// expression (assuming there are no other breaks,
608+
// this implies that the type of the block will be
609+
// `!`).
610+
if self.diverges.is_always() {
611+
// we don't even make an attempt at coercion
612+
self.table.new_maybe_never_type_var()
613+
} else {
614+
self.coerce(&Ty::unit(), expected.coercion_target());
615+
Ty::unit()
616+
}
559617
};
560-
if diverges {
561-
Ty::simple(TypeCtor::Never)
562-
} else {
563-
ty
564-
}
618+
ty
565619
}
566620

567621
fn infer_method_call(

crates/ra_hir_ty/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,10 @@ impl Ty {
730730
}
731731
}
732732

733+
pub fn is_never(&self) -> bool {
734+
matches!(self, Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }))
735+
}
736+
733737
/// If this is a `dyn Trait` type, this returns the `Trait` part.
734738
pub fn dyn_trait_ref(&self) -> Option<&TraitRef> {
735739
match self {

crates/ra_hir_ty/src/tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,3 +518,21 @@ fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
518518

519519
assert_snapshot!(diagnostics, @"");
520520
}
521+
522+
#[test]
523+
fn break_outside_of_loop() {
524+
let diagnostics = TestDB::with_files(
525+
r"
526+
//- /lib.rs
527+
fn foo() {
528+
break;
529+
}
530+
",
531+
)
532+
.diagnostics()
533+
.0;
534+
535+
assert_snapshot!(diagnostics, @r###""break": break outside of loop
536+
"###
537+
);
538+
}

crates/ra_hir_ty/src/tests/coercion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ fn foo() -> u32 {
384384
}
385385
"#, true),
386386
@r###"
387-
17..40 '{ ...own; }': !
387+
17..40 '{ ...own; }': u32
388388
23..37 'return unknown': !
389389
30..37 'unknown': u32
390390
"###
@@ -514,7 +514,7 @@ fn foo() {
514514
27..103 '{ ... }': &u32
515515
37..82 'if tru... }': ()
516516
40..44 'true': bool
517-
45..82 '{ ... }': !
517+
45..82 '{ ... }': ()
518518
59..71 'return &1u32': !
519519
66..71 '&1u32': &u32
520520
67..71 '1u32': u32

crates/ra_hir_ty/src/tests/macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ fn spam() {
197197
!0..6 '1isize': isize
198198
!0..6 '1isize': isize
199199
!0..6 '1isize': isize
200-
54..457 '{ ...!(); }': !
200+
54..457 '{ ...!(); }': ()
201201
88..109 'spam!(...am!())': {unknown}
202202
115..134 'for _ ...!() {}': ()
203203
119..120 '_': {unknown}

0 commit comments

Comments
 (0)