Skip to content

Commit

Permalink
Suggest boxing both arms of if expr if that solves divergent arms inv…
Browse files Browse the repository at this point in the history
…olving `impl Trait`

When encountering the following

```rust
// run-rustfix
trait Trait {}
struct Struct;
impl Trait for Struct {}
fn foo() -> Box<dyn Trait> {
    Box::new(Struct)
}
fn bar() -> impl Trait {
    Struct
}
fn main() {
    let _ = if true {
        Struct
    } else {
        foo() //~ ERROR E0308
    };
    let _ = if true {
        foo()
    } else {
        Struct //~ ERROR E0308
    };
    let _ = if true {
        Struct
    } else {
        bar() // impl Trait
    };
    let _ = if true {
        bar() // impl Trait
    } else {
        Struct
    };
}
```

suggest boxing both arms

```rust
    let _ = if true {
        Box::new(Struct) as Box<dyn Trait>
    } else {
        Box::new(bar())
    };
    let _ = if true {
        Box::new(bar()) as Box<dyn Trait>
    } else {
        Box::new(Struct)
    };
```
  • Loading branch information
estebank committed Jan 23, 2024
1 parent ac56a2b commit 34f4f3d
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 19 deletions.
86 changes: 70 additions & 16 deletions compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,30 +294,84 @@ impl<T> Trait<T> for X {
);
}
}
(ty::Alias(ty::Opaque, alias), _) | (_, ty::Alias(ty::Opaque, alias))
if alias.def_id.is_local()
(_, ty::Alias(ty::Opaque, opaque_ty))
| (ty::Alias(ty::Opaque, opaque_ty), _) => {
if opaque_ty.def_id.is_local()
&& matches!(
tcx.def_kind(body_owner_def_id),
DefKind::Fn
| DefKind::Static(_)
| DefKind::Const
| DefKind::AssocFn
| DefKind::AssocConst
) =>
{
if tcx.is_type_alias_impl_trait(alias.def_id) {
if !tcx
)
&& tcx.is_type_alias_impl_trait(opaque_ty.def_id)
&& !tcx
.opaque_types_defined_by(body_owner_def_id.expect_local())
.contains(&alias.def_id.expect_local())
{
let sp = tcx
.def_ident_span(body_owner_def_id)
.unwrap_or_else(|| tcx.def_span(body_owner_def_id));
diag.span_note(
sp,
"\
this item must have the opaque type in its signature \
in order to be able to register hidden types",
.contains(&opaque_ty.def_id.expect_local())
{
let sp = tcx
.def_ident_span(body_owner_def_id)
.unwrap_or_else(|| tcx.def_span(body_owner_def_id));
diag.span_note(
sp,
"this item must have the opaque type in its signature in order to \
be able to register hidden types",
);
}
// If two if arms can be coerced to a trait object, provide a structured
// suggestion.
let ObligationCauseCode::IfExpression(cause) = cause.code() else {
return;
};
let hir::Node::Block(blk) = self.tcx.hir_node(cause.then_id) else {
return;
};
let Some(then) = blk.expr else {
return;
};
let hir::Node::Block(blk) = self.tcx.hir_node(cause.else_id) else {
return;
};
let Some(else_) = blk.expr else {
return;
};
let expected = match values.found.kind() {
ty::Alias(..) => values.expected,
_ => values.found,
};
let preds = tcx.explicit_item_bounds(opaque_ty.def_id);
for (pred, _span) in preds.skip_binder() {
let ty::ClauseKind::Trait(trait_predicate) = pred.kind().skip_binder()
else {
continue;
};
if trait_predicate.polarity != ty::ImplPolarity::Positive {
continue;
}
let def_id = trait_predicate.def_id();
let mut impl_def_ids = vec![];
tcx.for_each_relevant_impl(def_id, expected, |did| {
impl_def_ids.push(did)
});
if let [_] = &impl_def_ids[..] {
let trait_name = tcx.item_name(def_id);
diag.multipart_suggestion(
format!(
"`{expected}` implements `{trait_name}` so you can box \
both arms and coerce to the trait object \
`Box<dyn {trait_name}>`",
),
vec![
(then.span.shrink_to_lo(), "Box::new(".to_string()),
(
then.span.shrink_to_hi(),
format!(") as Box<dyn {}>", tcx.def_path_str(def_id)),
),
(else_.span.shrink_to_lo(), "Box::new(".to_string()),
(else_.span.shrink_to_hi(), ")".to_string()),
],
MachineApplicable,
);
}
}
Expand Down
13 changes: 13 additions & 0 deletions tests/ui/typeck/suggest-box-on-divergent-if-else-arms.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ impl Trait for Struct {}
fn foo() -> Box<dyn Trait> {
Box::new(Struct)
}
fn bar() -> impl Trait {
Struct
}
fn main() {
let _ = if true {
Box::new(Struct)
Expand All @@ -16,4 +19,14 @@ fn main() {
} else {
Box::new(Struct) //~ ERROR E0308
};
let _ = if true {
Box::new(Struct) as Box<dyn Trait>
} else {
Box::new(bar()) //~ ERROR E0308
};
let _ = if true {
Box::new(bar()) as Box<dyn Trait>
} else {
Box::new(Struct) //~ ERROR E0308
};
}
13 changes: 13 additions & 0 deletions tests/ui/typeck/suggest-box-on-divergent-if-else-arms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ impl Trait for Struct {}
fn foo() -> Box<dyn Trait> {
Box::new(Struct)
}
fn bar() -> impl Trait {
Struct
}
fn main() {
let _ = if true {
Struct
Expand All @@ -16,4 +19,14 @@ fn main() {
} else {
Struct //~ ERROR E0308
};
let _ = if true {
Struct
} else {
bar() //~ ERROR E0308
};
let _ = if true {
bar()
} else {
Struct //~ ERROR E0308
};
}
56 changes: 53 additions & 3 deletions tests/ui/typeck/suggest-box-on-divergent-if-else-arms.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0308]: `if` and `else` have incompatible types
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:12:9
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:15:9
|
LL | let _ = if true {
| _____________-
Expand All @@ -19,7 +19,7 @@ LL | Box::new(Struct)
| +++++++++ +

error[E0308]: `if` and `else` have incompatible types
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:17:9
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:20:9
|
LL | let _ = if true {
| _____________-
Expand All @@ -39,6 +39,56 @@ help: store this in the heap by calling `Box::new`
LL | Box::new(Struct)
| +++++++++ +

error: aborting due to 2 previous errors
error[E0308]: `if` and `else` have incompatible types
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:25:9
|
LL | fn bar() -> impl Trait {
| ---------- the found opaque type
...
LL | let _ = if true {
| _____________-
LL | | Struct
| | ------ expected because of this
LL | | } else {
LL | | bar()
| | ^^^^^ expected `Struct`, found opaque type
LL | | };
| |_____- `if` and `else` have incompatible types
|
= note: expected struct `Struct`
found opaque type `impl Trait`
help: `Struct` implements `Trait` so you can box both arms and coerce to the trait object `Box<dyn Trait>`
|
LL ~ Box::new(Struct) as Box<dyn Trait>
LL | } else {
LL ~ Box::new(bar())
|

error[E0308]: `if` and `else` have incompatible types
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:30:9
|
LL | fn bar() -> impl Trait {
| ---------- the expected opaque type
...
LL | let _ = if true {
| _____________-
LL | | bar()
| | ----- expected because of this
LL | | } else {
LL | | Struct
| | ^^^^^^ expected opaque type, found `Struct`
LL | | };
| |_____- `if` and `else` have incompatible types
|
= note: expected opaque type `impl Trait`
found struct `Struct`
help: `Struct` implements `Trait` so you can box both arms and coerce to the trait object `Box<dyn Trait>`
|
LL ~ Box::new(bar()) as Box<dyn Trait>
LL | } else {
LL ~ Box::new(Struct)
|

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0308`.

0 comments on commit 34f4f3d

Please sign in to comment.