Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit af30656

Browse files
committed
Auto merge of rust-lang#14436 - lowr:patch/normalize-assoc-type-in-path-expr, r=HKalbasi
Normalize associated types in paths in expressions Part of rust-lang#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 rust-lang#14393 to be currently broken.
2 parents a646439 + 6447d48 commit af30656

File tree

6 files changed

+232
-53
lines changed

6 files changed

+232
-53
lines changed

crates/hir-def/src/resolver.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ enum Scope {
7979
ExprScope(ExprScope),
8080
}
8181

82-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
82+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8383
pub enum TypeNs {
8484
SelfType(ImplId),
8585
GenericParam(TypeParamId),

crates/hir-ty/src/infer.rs

Lines changed: 69 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use std::sync::Arc;
1717
use std::{convert::identity, ops::Index};
1818

19-
use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags};
19+
use chalk_ir::{cast::Cast, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags};
2020
use either::Either;
2121
use hir_def::{
2222
body::Body,
@@ -37,10 +37,10 @@ use rustc_hash::{FxHashMap, FxHashSet};
3737
use stdx::{always, never};
3838

3939
use crate::{
40-
db::HirDatabase, fold_tys, fold_tys_and_consts, infer::coerce::CoerceMany,
41-
lower::ImplTraitLoweringMode, static_lifetime, to_assoc_type_id, AliasEq, AliasTy, Const,
42-
DomainGoal, GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, RpitId,
43-
Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
40+
db::HirDatabase, fold_tys, infer::coerce::CoerceMany, lower::ImplTraitLoweringMode,
41+
static_lifetime, to_assoc_type_id, AliasEq, AliasTy, DomainGoal, GenericArg, Goal, ImplTraitId,
42+
InEnvironment, Interner, ProjectionTy, RpitId, Substitution, TraitRef, Ty, TyBuilder, TyExt,
43+
TyKind,
4444
};
4545

4646
// This lint has a false positive here. See the link below for details.
@@ -744,43 +744,13 @@ impl<'a> InferenceContext<'a> {
744744
self.result.standard_types.unknown.clone()
745745
}
746746

747-
/// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it.
748-
fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
749-
let data = c.data(Interner);
750-
match &data.value {
751-
ConstValue::Concrete(cc) => match cc.interned {
752-
crate::ConstScalar::Unknown => self.table.new_const_var(data.ty.clone()),
753-
_ => c,
754-
},
755-
_ => c,
756-
}
757-
}
758-
759747
/// Replaces `Ty::Error` by a new type var, so we can maybe still infer it.
760748
fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
761-
match ty.kind(Interner) {
762-
TyKind::Error => self.table.new_type_var(),
763-
TyKind::InferenceVar(..) => {
764-
let ty_resolved = self.resolve_ty_shallow(&ty);
765-
if ty_resolved.is_unknown() {
766-
self.table.new_type_var()
767-
} else {
768-
ty
769-
}
770-
}
771-
_ => ty,
772-
}
749+
self.table.insert_type_vars_shallow(ty)
773750
}
774751

775752
fn insert_type_vars(&mut self, ty: Ty) -> Ty {
776-
fold_tys_and_consts(
777-
ty,
778-
|x, _| match x {
779-
Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)),
780-
Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)),
781-
},
782-
DebruijnIndex::INNERMOST,
783-
)
753+
self.table.insert_type_vars(ty)
784754
}
785755

786756
fn push_obligation(&mut self, o: DomainGoal) {
@@ -909,8 +879,6 @@ impl<'a> InferenceContext<'a> {
909879
None => return (self.err_ty(), None),
910880
};
911881
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
912-
// FIXME: this should resolve assoc items as well, see this example:
913-
// https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521
914882
let (resolution, unresolved) = if value_ns {
915883
match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path) {
916884
Some(ResolveValueResult::ValueNs(value)) => match value {
@@ -964,8 +932,68 @@ impl<'a> InferenceContext<'a> {
964932
TypeNs::SelfType(impl_id) => {
965933
let generics = crate::utils::generics(self.db.upcast(), impl_id.into());
966934
let substs = generics.placeholder_subst(self.db);
967-
let ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs);
968-
self.resolve_variant_on_alias(ty, unresolved, mod_path)
935+
let mut ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs);
936+
937+
let Some(mut remaining_idx) = unresolved else {
938+
return self.resolve_variant_on_alias(ty, None, mod_path);
939+
};
940+
941+
let mut remaining_segments = path.segments().skip(remaining_idx);
942+
943+
// We need to try resolving unresolved segments one by one because each may resolve
944+
// to a projection, which `TyLoweringContext` cannot handle on its own.
945+
while !remaining_segments.is_empty() {
946+
let resolved_segment = path.segments().get(remaining_idx - 1).unwrap();
947+
let current_segment = remaining_segments.take(1);
948+
949+
// If we can resolve to an enum variant, it takes priority over associated type
950+
// of the same name.
951+
if let Some((AdtId::EnumId(id), _)) = ty.as_adt() {
952+
let enum_data = self.db.enum_data(id);
953+
let name = current_segment.first().unwrap().name;
954+
if let Some(local_id) = enum_data.variant(name) {
955+
let variant = EnumVariantId { parent: id, local_id };
956+
return if remaining_segments.len() == 1 {
957+
(ty, Some(variant.into()))
958+
} else {
959+
// We still have unresolved paths, but enum variants never have
960+
// associated types!
961+
(self.err_ty(), None)
962+
};
963+
}
964+
}
965+
966+
// `lower_partly_resolved_path()` returns `None` as type namespace unless
967+
// `remaining_segments` is empty, which is never the case here. We don't know
968+
// which namespace the new `ty` is in until normalized anyway.
969+
(ty, _) = ctx.lower_partly_resolved_path(
970+
resolution,
971+
resolved_segment,
972+
current_segment,
973+
false,
974+
);
975+
976+
ty = self.table.insert_type_vars(ty);
977+
ty = self.table.normalize_associated_types_in(ty);
978+
ty = self.table.resolve_ty_shallow(&ty);
979+
if ty.is_unknown() {
980+
return (self.err_ty(), None);
981+
}
982+
983+
// FIXME(inherent_associated_types): update `resolution` based on `ty` here.
984+
remaining_idx += 1;
985+
remaining_segments = remaining_segments.skip(1);
986+
}
987+
988+
let variant = ty.as_adt().and_then(|(id, _)| match id {
989+
AdtId::StructId(s) => Some(VariantId::StructId(s)),
990+
AdtId::UnionId(u) => Some(VariantId::UnionId(u)),
991+
AdtId::EnumId(_) => {
992+
// FIXME Error E0071, expected struct, variant or union type, found enum `Foo`
993+
None
994+
}
995+
});
996+
(ty, variant)
969997
}
970998
TypeNs::TypeAliasId(it) => {
971999
let container = it.lookup(self.db.upcast()).container;

crates/hir-ty/src/infer/path.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ impl<'a> InferenceContext<'a> {
3030

3131
fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> {
3232
let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
33-
let Some(last) = path.segments().last() else { return None };
34-
let ty = self.make_ty(type_ref);
35-
let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
33+
let last = path.segments().last()?;
34+
35+
// Don't use `self.make_ty()` here as we need `orig_ns`.
3636
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
37-
let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty);
37+
let (ty, orig_ns) = ctx.lower_ty_ext(type_ref);
38+
let ty = self.table.insert_type_vars(ty);
39+
let ty = self.table.normalize_associated_types_in(ty);
40+
41+
let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
42+
let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty);
43+
let ty = self.table.insert_type_vars(ty);
44+
let ty = self.table.normalize_associated_types_in(ty);
3845
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
3946
} else {
4047
// FIXME: report error, unresolved first path segment
@@ -169,7 +176,7 @@ impl<'a> InferenceContext<'a> {
169176
) -> Option<(ValueNs, Substitution)> {
170177
let trait_ = trait_ref.hir_trait_id();
171178
let item =
172-
self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id)).find_map(|item| {
179+
self.db.trait_data(trait_).items.iter().map(|(_name, id)| *id).find_map(|item| {
173180
match item {
174181
AssocItemId::FunctionId(func) => {
175182
if segment.name == &self.db.function_data(func).name {
@@ -288,7 +295,7 @@ impl<'a> InferenceContext<'a> {
288295
name: &Name,
289296
id: ExprOrPatId,
290297
) -> Option<(ValueNs, Substitution)> {
291-
let ty = self.resolve_ty_shallow(ty);
298+
let ty = self.resolve_ty_shallow(&ty);
292299
let (enum_id, subst) = match ty.as_adt() {
293300
Some((AdtId::EnumId(e), subst)) => (e, subst),
294301
_ => return None,

crates/hir-ty/src/infer/unify.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ use chalk_ir::{
77
IntTy, TyVariableKind, UniverseIndex,
88
};
99
use chalk_solve::infer::ParameterEnaVariableExt;
10+
use either::Either;
1011
use ena::unify::UnifyKey;
1112
use hir_expand::name;
1213
use stdx::never;
1314

1415
use super::{InferOk, InferResult, InferenceContext, TypeError};
1516
use crate::{
16-
db::HirDatabase, fold_tys, static_lifetime, to_chalk_trait_id, traits::FnTrait, AliasEq,
17-
AliasTy, BoundVar, Canonical, Const, DebruijnIndex, GenericArg, GenericArgData, Goal, Guidance,
18-
InEnvironment, InferenceVar, Interner, Lifetime, ParamKind, ProjectionTy, ProjectionTyExt,
19-
Scalar, Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind,
17+
db::HirDatabase, fold_tys, fold_tys_and_consts, static_lifetime, to_chalk_trait_id,
18+
traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical, Const, ConstValue, DebruijnIndex,
19+
GenericArg, GenericArgData, Goal, Guidance, InEnvironment, InferenceVar, Interner, Lifetime,
20+
ParamKind, ProjectionTy, ProjectionTyExt, Scalar, Solution, Substitution, TraitEnvironment, Ty,
21+
TyBuilder, TyExt, TyKind, VariableKind,
2022
};
2123

2224
impl<'a> InferenceContext<'a> {
@@ -717,6 +719,45 @@ impl<'a> InferenceTable<'a> {
717719
None
718720
}
719721
}
722+
723+
pub(super) fn insert_type_vars(&mut self, ty: Ty) -> Ty {
724+
fold_tys_and_consts(
725+
ty,
726+
|x, _| match x {
727+
Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)),
728+
Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)),
729+
},
730+
DebruijnIndex::INNERMOST,
731+
)
732+
}
733+
734+
/// Replaces `Ty::Error` by a new type var, so we can maybe still infer it.
735+
pub(super) fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
736+
match ty.kind(Interner) {
737+
TyKind::Error => self.new_type_var(),
738+
TyKind::InferenceVar(..) => {
739+
let ty_resolved = self.resolve_ty_shallow(&ty);
740+
if ty_resolved.is_unknown() {
741+
self.new_type_var()
742+
} else {
743+
ty
744+
}
745+
}
746+
_ => ty,
747+
}
748+
}
749+
750+
/// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it.
751+
pub(super) fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
752+
let data = c.data(Interner);
753+
match &data.value {
754+
ConstValue::Concrete(cc) => match cc.interned {
755+
crate::ConstScalar::Unknown => self.new_const_var(data.ty.clone()),
756+
_ => c,
757+
},
758+
_ => c,
759+
}
760+
}
720761
}
721762

722763
impl<'a> fmt::Debug for InferenceTable<'a> {

crates/hir-ty/src/lower.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ impl ImplTraitLoweringState {
103103
#[derive(Debug)]
104104
pub struct TyLoweringContext<'a> {
105105
pub db: &'a dyn HirDatabase,
106-
pub resolver: &'a Resolver,
106+
resolver: &'a Resolver,
107107
in_binders: DebruijnIndex,
108108
/// Note: Conceptually, it's thinkable that we could be in a location where
109109
/// some type params should be represented as placeholders, and others

crates/hir-ty/src/tests/traits.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4183,3 +4183,106 @@ fn test() {
41834183
"#,
41844184
);
41854185
}
4186+
4187+
#[test]
4188+
fn associated_type_in_struct_expr_path() {
4189+
// FIXME: All annotation should be resolvable.
4190+
// For lines marked as unstable, see rust-lang/rust#86935.
4191+
// FIXME: Remove the comments once stablized.
4192+
check_types(
4193+
r#"
4194+
trait Trait {
4195+
type Assoc;
4196+
fn f();
4197+
}
4198+
4199+
struct S { x: u32 }
4200+
4201+
impl Trait for () {
4202+
type Assoc = S;
4203+
4204+
fn f() {
4205+
let x = 42;
4206+
let a = Self::Assoc { x };
4207+
// ^ S
4208+
let a = <Self>::Assoc { x }; // unstable
4209+
// ^ {unknown}
4210+
4211+
// should be `Copy` but we don't track ownership anyway.
4212+
let value = S { x };
4213+
if let Self::Assoc { x } = value {}
4214+
// ^ u32
4215+
if let <Self>::Assoc { x } = value {} // unstable
4216+
// ^ {unknown}
4217+
}
4218+
}
4219+
"#,
4220+
);
4221+
}
4222+
4223+
#[test]
4224+
fn associted_type_in_struct_expr_path_enum() {
4225+
// FIXME: All annotation should be resolvable.
4226+
// For lines marked as unstable, see rust-lang/rust#86935.
4227+
// FIXME: Remove the comments once stablized.
4228+
check_types(
4229+
r#"
4230+
trait Trait {
4231+
type Assoc;
4232+
fn f();
4233+
}
4234+
4235+
enum E {
4236+
Unit,
4237+
Struct { x: u32 },
4238+
}
4239+
4240+
impl Trait for () {
4241+
type Assoc = E;
4242+
4243+
fn f() {
4244+
let a = Self::Assoc::Unit;
4245+
// ^ E
4246+
let a = <Self>::Assoc::Unit;
4247+
// ^ E
4248+
let a = <Self::Assoc>::Unit;
4249+
// ^ E
4250+
let a = <<Self>::Assoc>::Unit;
4251+
// ^ E
4252+
4253+
// should be `Copy` but we don't track ownership anyway.
4254+
let value = E::Unit;
4255+
if let Self::Assoc::Unit = value {}
4256+
// ^^^^^^^^^^^^^^^^^ E
4257+
if let <Self>::Assoc::Unit = value {}
4258+
// ^^^^^^^^^^^^^^^^^^^ E
4259+
if let <Self::Assoc>::Unit = value {}
4260+
// ^^^^^^^^^^^^^^^^^^^ E
4261+
if let <<Self>::Assoc>::Unit = value {}
4262+
// ^^^^^^^^^^^^^^^^^^^^^ E
4263+
4264+
let x = 42;
4265+
let a = Self::Assoc::Struct { x };
4266+
// ^ E
4267+
let a = <Self>::Assoc::Struct { x }; // unstable
4268+
// ^ {unknown}
4269+
let a = <Self::Assoc>::Struct { x }; // unstable
4270+
// ^ {unknown}
4271+
let a = <<Self>::Assoc>::Struct { x }; // unstable
4272+
// ^ {unknown}
4273+
4274+
// should be `Copy` but we don't track ownership anyway.
4275+
let value = E::Struct { x: 42 };
4276+
if let Self::Assoc::Struct { x } = value {}
4277+
// ^ u32
4278+
if let <Self>::Assoc::Struct { x } = value {} // unstable
4279+
// ^ {unknown}
4280+
if let <Self::Assoc>::Struct { x } = value {} // unstable
4281+
// ^ {unknown}
4282+
if let <<Self>::Assoc>::Struct { x } = value {} // unstable
4283+
// ^ {unknown}
4284+
}
4285+
}
4286+
"#,
4287+
);
4288+
}

0 commit comments

Comments
 (0)