Repository: rust-lang/rust-analyzer
Pull request #13264 (2022-09-19 19:54:19)

Ensure at least one trait bound in `TyKind::DynTy`
One would expect `TyKind::DynTy` to have at least one trait bound, but we may produce a dyn type with no trait bounds at all. This patch prevents it by returning `TyKind::Error` in such cases.

An "empty" dyn type would have caused panic during method resolution without #13257. Although already fixed, I think an invariant to never produce such types would help prevent similar problems in the future.


Patch:
diff --git a/crates/hir-ty/src/chalk_ext.rs b/crates/hir-ty/src/chalk_ext.rs
index 4a5533c648..ed97bd2da4 100644
--- a/crates/hir-ty/src/chalk_ext.rs
+++ b/crates/hir-ty/src/chalk_ext.rs
@@ -166,6 +166,8 @@ impl TyExt for Ty {
         let trait_ref = match self.kind(Interner) {
             // The principal trait bound should be the first element of the bounds. This is an
             // invariant ensured by `TyLoweringContext::lower_dyn_trait()`.
+            // FIXME: dyn types may not have principal trait and we don't want to return auto trait
+            // here.
             TyKind::Dyn(dyn_ty) => dyn_ty.bounds.skip_binders().interned().get(0).and_then(|b| {
                 match b.skip_binders() {
                     WhereClause::Implemented(trait_ref) => Some(trait_ref),
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index 532544fee5..e28c87dfa4 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -981,10 +981,11 @@ impl<'a> TyLoweringContext<'a> {
 
     fn lower_dyn_trait(&self, bounds: &[Interned<TypeBound>]) -> Ty {
         let self_ty = TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0)).intern(Interner);
-        // INVARIANT: The principal trait bound must come first. Others may be in any order but
-        // should be in the same order for the same set but possibly different order of bounds in
-        // the input.
-        // This invariant is used by `TyExt::dyn_trait()` and chalk.
+        // INVARIANT: The principal trait bound, if present, must come first. Others may be in any
+        // order but should be in the same order for the same set but possibly different order of
+        // bounds in the input.
+        // INVARIANT: If this function returns `DynTy`, there should be at least one trait bound.
+        // These invariants are utilized by `TyExt::dyn_trait()` and chalk.
         let bounds = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
             let mut bounds: Vec<_> = bounds
                 .iter()
@@ -1035,6 +1036,12 @@ impl<'a> TyLoweringContext<'a> {
                 return None;
             }
 
+            if bounds.first().and_then(|b| b.trait_id()).is_none() {
+                // When there's no trait bound, that's an error. This happens when the trait refs
+                // are unresolved.
+                return None;
+            }
+
             // As multiple occurrences of the same auto traits *are* permitted, we dedulicate the
             // bounds. We shouldn't have repeated elements besides auto traits at this point.
             bounds.dedup();
@@ -1046,7 +1053,8 @@ impl<'a> TyLoweringContext<'a> {
             let bounds = crate::make_single_type_binders(bounds);
             TyKind::Dyn(DynTy { bounds, lifetime: static_lifetime() }).intern(Interner)
         } else {
-            // FIXME: report error (additional non-auto traits or associated type rebound)
+            // FIXME: report error
+            // (additional non-auto traits, associated type rebound, or no resolved trait)
             TyKind::Error.intern(Interner)
         }
     }


Test patch:
diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index 23e51a9c16..47cc3341e7 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/crates/hir-ty/src/tests/regression.rs
@@ -1691,3 +1691,15 @@ fn macrostmts() -> u8 {
     "#,
     );
 }
+
+#[test]
+fn dyn_with_unresolved_trait() {
+    check_types(
+        r#"
+fn foo(a: &dyn DoesNotExist) {
+    a.bar();
+  //^&{unknown}
+}
+        "#,
+    );
+}