Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #14436 - lowr:patch/normalize-assoc-type-in-path-expr, r=HKalbasi
Normalize associated types in paths in expressions Part of #14393 When we resolve paths in expressions (either path expressions or paths in struct expressions), there's a need of projection normalization, which `TyLoweringContext` cannot do on its own. We've been properly applying normalization for paths in struct expressions without type anchor, but not for others: ```rust enum E { S { v: i32 } Empty, } impl Foo for Bar { type Assoc = E; fn foo() { let _ = Self::Assoc::S { v: 42 }; // path in struct expr without type anchor; we already support this let _ = <Self>::Assoc::S { v: 42 }; // path in struct expr with type anchor; resolves with this PR let _ = Self::Assoc::Empty; // path expr; resolves with this PR } } ``` With this PR we correctly resolve the whole path, but we need some more tweaks in HIR and/or IDE layers to properly resolve a qualifier (prefix) of such paths and provide IDE features that are pointed out in #14393 to be currently broken.
bors 2023-04-05
parent a646439 · parent 6447d48 · commit af30656
-rw-r--r--crates/hir-def/src/resolver.rs2
-rw-r--r--crates/hir-ty/src/infer.rs110
-rw-r--r--crates/hir-ty/src/infer/path.rs19
-rw-r--r--crates/hir-ty/src/infer/unify.rs49
-rw-r--r--crates/hir-ty/src/lower.rs2
-rw-r--r--crates/hir-ty/src/tests/traits.rs103
6 files changed, 232 insertions, 53 deletions
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 958882d43e..9afe07932e 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -79,7 +79,7 @@ enum Scope {
ExprScope(ExprScope),
}
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeNs {
SelfType(ImplId),
GenericParam(TypeParamId),
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 08ba80cdff..23206e93bc 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -16,7 +16,7 @@
use std::sync::Arc;
use std::{convert::identity, ops::Index};
-use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags};
+use chalk_ir::{cast::Cast, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags};
use either::Either;
use hir_def::{
body::Body,
@@ -37,10 +37,10 @@ use rustc_hash::{FxHashMap, FxHashSet};
use stdx::{always, never};
use crate::{
- db::HirDatabase, fold_tys, fold_tys_and_consts, infer::coerce::CoerceMany,
- lower::ImplTraitLoweringMode, static_lifetime, to_assoc_type_id, AliasEq, AliasTy, Const,
- DomainGoal, GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, RpitId,
- Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
+ db::HirDatabase, fold_tys, infer::coerce::CoerceMany, lower::ImplTraitLoweringMode,
+ static_lifetime, to_assoc_type_id, AliasEq, AliasTy, DomainGoal, GenericArg, Goal, ImplTraitId,
+ InEnvironment, Interner, ProjectionTy, RpitId, Substitution, TraitRef, Ty, TyBuilder, TyExt,
+ TyKind,
};
// This lint has a false positive here. See the link below for details.
@@ -744,43 +744,13 @@ impl<'a> InferenceContext<'a> {
self.result.standard_types.unknown.clone()
}
- /// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it.
- fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
- let data = c.data(Interner);
- match &data.value {
- ConstValue::Concrete(cc) => match cc.interned {
- crate::ConstScalar::Unknown => self.table.new_const_var(data.ty.clone()),
- _ => c,
- },
- _ => c,
- }
- }
-
/// Replaces `Ty::Error` by a new type var, so we can maybe still infer it.
fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
- match ty.kind(Interner) {
- TyKind::Error => self.table.new_type_var(),
- TyKind::InferenceVar(..) => {
- let ty_resolved = self.resolve_ty_shallow(&ty);
- if ty_resolved.is_unknown() {
- self.table.new_type_var()
- } else {
- ty
- }
- }
- _ => ty,
- }
+ self.table.insert_type_vars_shallow(ty)
}
fn insert_type_vars(&mut self, ty: Ty) -> Ty {
- fold_tys_and_consts(
- ty,
- |x, _| match x {
- Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)),
- Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)),
- },
- DebruijnIndex::INNERMOST,
- )
+ self.table.insert_type_vars(ty)
}
fn push_obligation(&mut self, o: DomainGoal) {
@@ -909,8 +879,6 @@ impl<'a> InferenceContext<'a> {
None => return (self.err_ty(), None),
};
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
- // FIXME: this should resolve assoc items as well, see this example:
- // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521
let (resolution, unresolved) = if value_ns {
match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path) {
Some(ResolveValueResult::ValueNs(value)) => match value {
@@ -964,8 +932,68 @@ impl<'a> InferenceContext<'a> {
TypeNs::SelfType(impl_id) => {
let generics = crate::utils::generics(self.db.upcast(), impl_id.into());
let substs = generics.placeholder_subst(self.db);
- let ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs);
- self.resolve_variant_on_alias(ty, unresolved, mod_path)
+ let mut ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs);
+
+ let Some(mut remaining_idx) = unresolved else {
+ return self.resolve_variant_on_alias(ty, None, mod_path);
+ };
+
+ let mut remaining_segments = path.segments().skip(remaining_idx);
+
+ // We need to try resolving unresolved segments one by one because each may resolve
+ // to a projection, which `TyLoweringContext` cannot handle on its own.
+ while !remaining_segments.is_empty() {
+ let resolved_segment = path.segments().get(remaining_idx - 1).unwrap();
+ let current_segment = remaining_segments.take(1);
+
+ // If we can resolve to an enum variant, it takes priority over associated type
+ // of the same name.
+ if let Some((AdtId::EnumId(id), _)) = ty.as_adt() {
+ let enum_data = self.db.enum_data(id);
+ let name = current_segment.first().unwrap().name;
+ if let Some(local_id) = enum_data.variant(name) {
+ let variant = EnumVariantId { parent: id, local_id };
+ return if remaining_segments.len() == 1 {
+ (ty, Some(variant.into()))
+ } else {
+ // We still have unresolved paths, but enum variants never have
+ // associated types!
+ (self.err_ty(), None)
+ };
+ }
+ }
+
+ // `lower_partly_resolved_path()` returns `None` as type namespace unless
+ // `remaining_segments` is empty, which is never the case here. We don't know
+ // which namespace the new `ty` is in until normalized anyway.
+ (ty, _) = ctx.lower_partly_resolved_path(
+ resolution,
+ resolved_segment,
+ current_segment,
+ false,
+ );
+
+ ty = self.table.insert_type_vars(ty);
+ ty = self.table.normalize_associated_types_in(ty);
+ ty = self.table.resolve_ty_shallow(&ty);
+ if ty.is_unknown() {
+ return (self.err_ty(), None);
+ }
+
+ // FIXME(inherent_associated_types): update `resolution` based on `ty` here.
+ remaining_idx += 1;
+ remaining_segments = remaining_segments.skip(1);
+ }
+
+ let variant = ty.as_adt().and_then(|(id, _)| match id {
+ AdtId::StructId(s) => Some(VariantId::StructId(s)),
+ AdtId::UnionId(u) => Some(VariantId::UnionId(u)),
+ AdtId::EnumId(_) => {
+ // FIXME Error E0071, expected struct, variant or union type, found enum `Foo`
+ None
+ }
+ });
+ (ty, variant)
}
TypeNs::TypeAliasId(it) => {
let container = it.lookup(self.db.upcast()).container;
diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs
index 266e410187..368c3f6524 100644
--- a/crates/hir-ty/src/infer/path.rs
+++ b/crates/hir-ty/src/infer/path.rs
@@ -30,11 +30,18 @@ impl<'a> InferenceContext<'a> {
fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> {
let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
- let Some(last) = path.segments().last() else { return None };
- let ty = self.make_ty(type_ref);
- let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
+ let last = path.segments().last()?;
+
+ // Don't use `self.make_ty()` here as we need `orig_ns`.
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
- let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty);
+ let (ty, orig_ns) = ctx.lower_ty_ext(type_ref);
+ let ty = self.table.insert_type_vars(ty);
+ let ty = self.table.normalize_associated_types_in(ty);
+
+ let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
+ let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty);
+ let ty = self.table.insert_type_vars(ty);
+ let ty = self.table.normalize_associated_types_in(ty);
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
} else {
// FIXME: report error, unresolved first path segment
@@ -169,7 +176,7 @@ impl<'a> InferenceContext<'a> {
) -> Option<(ValueNs, Substitution)> {
let trait_ = trait_ref.hir_trait_id();
let item =
- self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id)).find_map(|item| {
+ self.db.trait_data(trait_).items.iter().map(|(_name, id)| *id).find_map(|item| {
match item {
AssocItemId::FunctionId(func) => {
if segment.name == &self.db.function_data(func).name {
@@ -288,7 +295,7 @@ impl<'a> InferenceContext<'a> {
name: &Name,
id: ExprOrPatId,
) -> Option<(ValueNs, Substitution)> {
- let ty = self.resolve_ty_shallow(ty);
+ let ty = self.resolve_ty_shallow(&ty);
let (enum_id, subst) = match ty.as_adt() {
Some((AdtId::EnumId(e), subst)) => (e, subst),
_ => return None,
diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs
index f0e0714e1d..c73acb4b12 100644
--- a/crates/hir-ty/src/infer/unify.rs
+++ b/crates/hir-ty/src/infer/unify.rs
@@ -7,16 +7,18 @@ use chalk_ir::{
IntTy, TyVariableKind, UniverseIndex,
};
use chalk_solve::infer::ParameterEnaVariableExt;
+use either::Either;
use ena::unify::UnifyKey;
use hir_expand::name;
use stdx::never;
use super::{InferOk, InferResult, InferenceContext, TypeError};
use crate::{
- db::HirDatabase, fold_tys, static_lifetime, to_chalk_trait_id, traits::FnTrait, AliasEq,
- AliasTy, BoundVar, Canonical, Const, DebruijnIndex, GenericArg, GenericArgData, Goal, Guidance,
- InEnvironment, InferenceVar, Interner, Lifetime, ParamKind, ProjectionTy, ProjectionTyExt,
- Scalar, Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind,
+ db::HirDatabase, fold_tys, fold_tys_and_consts, static_lifetime, to_chalk_trait_id,
+ traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical, Const, ConstValue, DebruijnIndex,
+ GenericArg, GenericArgData, Goal, Guidance, InEnvironment, InferenceVar, Interner, Lifetime,
+ ParamKind, ProjectionTy, ProjectionTyExt, Scalar, Solution, Substitution, TraitEnvironment, Ty,
+ TyBuilder, TyExt, TyKind, VariableKind,
};
impl<'a> InferenceContext<'a> {
@@ -717,6 +719,45 @@ impl<'a> InferenceTable<'a> {
None
}
}
+
+ pub(super) fn insert_type_vars(&mut self, ty: Ty) -> Ty {
+ fold_tys_and_consts(
+ ty,
+ |x, _| match x {
+ Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)),
+ Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)),
+ },
+ DebruijnIndex::INNERMOST,
+ )
+ }
+
+ /// Replaces `Ty::Error` by a new type var, so we can maybe still infer it.
+ pub(super) fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
+ match ty.kind(Interner) {
+ TyKind::Error => self.new_type_var(),
+ TyKind::InferenceVar(..) => {
+ let ty_resolved = self.resolve_ty_shallow(&ty);
+ if ty_resolved.is_unknown() {
+ self.new_type_var()
+ } else {
+ ty
+ }
+ }
+ _ => ty,
+ }
+ }
+
+ /// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it.
+ pub(super) fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
+ let data = c.data(Interner);
+ match &data.value {
+ ConstValue::Concrete(cc) => match cc.interned {
+ crate::ConstScalar::Unknown => self.new_const_var(data.ty.clone()),
+ _ => c,
+ },
+ _ => c,
+ }
+ }
}
impl<'a> fmt::Debug for InferenceTable<'a> {
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index 797e9ad0e9..adadbb888b 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -103,7 +103,7 @@ impl ImplTraitLoweringState {
#[derive(Debug)]
pub struct TyLoweringContext<'a> {
pub db: &'a dyn HirDatabase,
- pub resolver: &'a Resolver,
+ resolver: &'a Resolver,
in_binders: DebruijnIndex,
/// Note: Conceptually, it's thinkable that we could be in a location where
/// some type params should be represented as placeholders, and others
diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs
index 813beaa364..17a0c12c3e 100644
--- a/crates/hir-ty/src/tests/traits.rs
+++ b/crates/hir-ty/src/tests/traits.rs
@@ -4183,3 +4183,106 @@ fn test() {
"#,
);
}
+
+#[test]
+fn associated_type_in_struct_expr_path() {
+ // FIXME: All annotation should be resolvable.
+ // For lines marked as unstable, see rust-lang/rust#86935.
+ // FIXME: Remove the comments once stablized.
+ check_types(
+ r#"
+trait Trait {
+ type Assoc;
+ fn f();
+}
+
+struct S { x: u32 }
+
+impl Trait for () {
+ type Assoc = S;
+
+ fn f() {
+ let x = 42;
+ let a = Self::Assoc { x };
+ // ^ S
+ let a = <Self>::Assoc { x }; // unstable
+ // ^ {unknown}
+
+ // should be `Copy` but we don't track ownership anyway.
+ let value = S { x };
+ if let Self::Assoc { x } = value {}
+ // ^ u32
+ if let <Self>::Assoc { x } = value {} // unstable
+ // ^ {unknown}
+ }
+}
+ "#,
+ );
+}
+
+#[test]
+fn associted_type_in_struct_expr_path_enum() {
+ // FIXME: All annotation should be resolvable.
+ // For lines marked as unstable, see rust-lang/rust#86935.
+ // FIXME: Remove the comments once stablized.
+ check_types(
+ r#"
+trait Trait {
+ type Assoc;
+ fn f();
+}
+
+enum E {
+ Unit,
+ Struct { x: u32 },
+}
+
+impl Trait for () {
+ type Assoc = E;
+
+ fn f() {
+ let a = Self::Assoc::Unit;
+ // ^ E
+ let a = <Self>::Assoc::Unit;
+ // ^ E
+ let a = <Self::Assoc>::Unit;
+ // ^ E
+ let a = <<Self>::Assoc>::Unit;
+ // ^ E
+
+ // should be `Copy` but we don't track ownership anyway.
+ let value = E::Unit;
+ if let Self::Assoc::Unit = value {}
+ // ^^^^^^^^^^^^^^^^^ E
+ if let <Self>::Assoc::Unit = value {}
+ // ^^^^^^^^^^^^^^^^^^^ E
+ if let <Self::Assoc>::Unit = value {}
+ // ^^^^^^^^^^^^^^^^^^^ E
+ if let <<Self>::Assoc>::Unit = value {}
+ // ^^^^^^^^^^^^^^^^^^^^^ E
+
+ let x = 42;
+ let a = Self::Assoc::Struct { x };
+ // ^ E
+ let a = <Self>::Assoc::Struct { x }; // unstable
+ // ^ {unknown}
+ let a = <Self::Assoc>::Struct { x }; // unstable
+ // ^ {unknown}
+ let a = <<Self>::Assoc>::Struct { x }; // unstable
+ // ^ {unknown}
+
+ // should be `Copy` but we don't track ownership anyway.
+ let value = E::Struct { x: 42 };
+ if let Self::Assoc::Struct { x } = value {}
+ // ^ u32
+ if let <Self>::Assoc::Struct { x } = value {} // unstable
+ // ^ {unknown}
+ if let <Self::Assoc>::Struct { x } = value {} // unstable
+ // ^ {unknown}
+ if let <<Self>::Assoc>::Struct { x } = value {} // unstable
+ // ^ {unknown}
+ }
+}
+ "#,
+ );
+}