Skip to content

Normalize associated types in paths in expressions #14436

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/hir-def/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
110 changes: 69 additions & 41 deletions crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use std::ops::Index;
use std::sync::Arc;

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,
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -850,8 +820,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 {
Expand Down Expand Up @@ -905,8 +873,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);
Comment on lines +885 to +888
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a bunch of unwrap in this function, which I think it is possible to remove by defining some windows like thing for PathSegments (or by using Itertools::tuple_windows) and a function which goes from a &PathSegment to a &PathSegments (similar to slice::from_ref).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we've got these path segment unwraps in more places (our API for paths isn't really that great currently)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so we can do it in a follow up.


// 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;
Expand Down
19 changes: 13 additions & 6 deletions crates/hir-ty/src/infer/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
49 changes: 45 additions & 4 deletions crates/hir-ty/src/infer/unify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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> {
Expand Down
2 changes: 1 addition & 1 deletion crates/hir-ty/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
103 changes: 103 additions & 0 deletions crates/hir-ty/src/tests/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4181,3 +4181,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}
}
}
"#,
);
}