Skip to content

Commit 7449f9f

Browse files
committed
Auto merge of rust-lang#13894 - lowr:patch/fallback-before-final-obligation-resolution, r=lnicola
Apply fallback before final obligation resolution Fixes rust-lang#13249 Fixes rust-lang#13518 We've been applying fallback to type variables independently even when there are some unresolved obligations that associate them. This PR applies fallback to unresolved scalar type variables before the final attempt of resolving obligations, which enables us to infer more. Unlike rustc, which has separate storages for each kind of type variables, we currently don't have a way to retrieve only integer/float type variables without folding/visiting every single type we've inferred. I've repurposed `TypeVariableData` as bitflags that also hold the kind of the type variable it's referring to so that we can "reconstruct" scalar type variables from their indices. This PR increases the number of ??ty for rust-analyzer repo not because we regress and fail to infer the existing code but because we fail to infer the new code. It seems we have problems inferring some functions bitflags produces.
2 parents f1a9901 + d01630c commit 7449f9f

File tree

5 files changed

+139
-15
lines changed

5 files changed

+139
-15
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hir-ty/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ doctest = false
1313
cov-mark = "2.0.0-pre.1"
1414
itertools = "0.10.5"
1515
arrayvec = "0.7.2"
16+
bitflags = "1.3.2"
1617
smallvec = "1.10.0"
1718
ena = "0.14.0"
1819
tracing = "0.1.35"

crates/hir-ty/src/infer.rs

+2
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ impl<'a> InferenceContext<'a> {
512512
fn resolve_all(self) -> InferenceResult {
513513
let InferenceContext { mut table, mut result, .. } = self;
514514

515+
table.fallback_if_possible();
516+
515517
// FIXME resolve obligations as well (use Guidance if necessary)
516518
table.resolve_obligations_as_possible();
517519

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

+70-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Unification and canonicalization logic.
22
3-
use std::{fmt, mem, sync::Arc};
3+
use std::{fmt, iter, mem, sync::Arc};
44

55
use chalk_ir::{
66
cast::Cast, fold::TypeFoldable, interner::HasInterner, zip::Zip, CanonicalVarKind, FloatTy,
@@ -128,9 +128,13 @@ pub(crate) fn unify(
128128
))
129129
}
130130

131-
#[derive(Copy, Clone, Debug)]
132-
pub(crate) struct TypeVariableData {
133-
diverging: bool,
131+
bitflags::bitflags! {
132+
#[derive(Default)]
133+
pub(crate) struct TypeVariableFlags: u8 {
134+
const DIVERGING = 1 << 0;
135+
const INTEGER = 1 << 1;
136+
const FLOAT = 1 << 2;
137+
}
134138
}
135139

136140
type ChalkInferenceTable = chalk_solve::infer::InferenceTable<Interner>;
@@ -140,14 +144,14 @@ pub(crate) struct InferenceTable<'a> {
140144
pub(crate) db: &'a dyn HirDatabase,
141145
pub(crate) trait_env: Arc<TraitEnvironment>,
142146
var_unification_table: ChalkInferenceTable,
143-
type_variable_table: Vec<TypeVariableData>,
147+
type_variable_table: Vec<TypeVariableFlags>,
144148
pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
145149
}
146150

147151
pub(crate) struct InferenceTableSnapshot {
148152
var_table_snapshot: chalk_solve::infer::InferenceSnapshot<Interner>,
149153
pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
150-
type_variable_table_snapshot: Vec<TypeVariableData>,
154+
type_variable_table_snapshot: Vec<TypeVariableFlags>,
151155
}
152156

153157
impl<'a> InferenceTable<'a> {
@@ -169,27 +173,27 @@ impl<'a> InferenceTable<'a> {
169173
/// result.
170174
pub(super) fn propagate_diverging_flag(&mut self) {
171175
for i in 0..self.type_variable_table.len() {
172-
if !self.type_variable_table[i].diverging {
176+
if !self.type_variable_table[i].contains(TypeVariableFlags::DIVERGING) {
173177
continue;
174178
}
175179
let v = InferenceVar::from(i as u32);
176180
let root = self.var_unification_table.inference_var_root(v);
177181
if let Some(data) = self.type_variable_table.get_mut(root.index() as usize) {
178-
data.diverging = true;
182+
*data |= TypeVariableFlags::DIVERGING;
179183
}
180184
}
181185
}
182186

183187
pub(super) fn set_diverging(&mut self, iv: InferenceVar, diverging: bool) {
184-
self.type_variable_table[iv.index() as usize].diverging = diverging;
188+
self.type_variable_table[iv.index() as usize].set(TypeVariableFlags::DIVERGING, diverging);
185189
}
186190

187191
fn fallback_value(&self, iv: InferenceVar, kind: TyVariableKind) -> Ty {
188192
match kind {
189193
_ if self
190194
.type_variable_table
191195
.get(iv.index() as usize)
192-
.map_or(false, |data| data.diverging) =>
196+
.map_or(false, |data| data.contains(TypeVariableFlags::DIVERGING)) =>
193197
{
194198
TyKind::Never
195199
}
@@ -247,18 +251,24 @@ impl<'a> InferenceTable<'a> {
247251
}
248252

249253
fn extend_type_variable_table(&mut self, to_index: usize) {
250-
self.type_variable_table.extend(
251-
(0..1 + to_index - self.type_variable_table.len())
252-
.map(|_| TypeVariableData { diverging: false }),
253-
);
254+
let count = to_index - self.type_variable_table.len() + 1;
255+
self.type_variable_table.extend(iter::repeat(TypeVariableFlags::default()).take(count));
254256
}
255257

256258
fn new_var(&mut self, kind: TyVariableKind, diverging: bool) -> Ty {
257259
let var = self.var_unification_table.new_variable(UniverseIndex::ROOT);
258260
// Chalk might have created some type variables for its own purposes that we don't know about...
259261
self.extend_type_variable_table(var.index() as usize);
260262
assert_eq!(var.index() as usize, self.type_variable_table.len() - 1);
261-
self.type_variable_table[var.index() as usize].diverging = diverging;
263+
let flags = self.type_variable_table.get_mut(var.index() as usize).unwrap();
264+
if diverging {
265+
*flags |= TypeVariableFlags::DIVERGING;
266+
}
267+
if matches!(kind, TyVariableKind::Integer) {
268+
*flags |= TypeVariableFlags::INTEGER;
269+
} else if matches!(kind, TyVariableKind::Float) {
270+
*flags |= TypeVariableFlags::FLOAT;
271+
}
262272
var.to_ty_with_kind(Interner, kind)
263273
}
264274

@@ -340,6 +350,51 @@ impl<'a> InferenceTable<'a> {
340350
self.resolve_with_fallback(t, &|_, _, d, _| d)
341351
}
342352

353+
/// Apply a fallback to unresolved scalar types. Integer type variables and float type
354+
/// variables are replaced with i32 and f64, respectively.
355+
///
356+
/// This method is only intended to be called just before returning inference results (i.e. in
357+
/// `InferenceContext::resolve_all()`).
358+
///
359+
/// FIXME: This method currently doesn't apply fallback to unconstrained general type variables
360+
/// whereas rustc replaces them with `()` or `!`.
361+
pub(super) fn fallback_if_possible(&mut self) {
362+
let int_fallback = TyKind::Scalar(Scalar::Int(IntTy::I32)).intern(Interner);
363+
let float_fallback = TyKind::Scalar(Scalar::Float(FloatTy::F64)).intern(Interner);
364+
365+
let scalar_vars: Vec<_> = self
366+
.type_variable_table
367+
.iter()
368+
.enumerate()
369+
.filter_map(|(index, flags)| {
370+
let kind = if flags.contains(TypeVariableFlags::INTEGER) {
371+
TyVariableKind::Integer
372+
} else if flags.contains(TypeVariableFlags::FLOAT) {
373+
TyVariableKind::Float
374+
} else {
375+
return None;
376+
};
377+
378+
// FIXME: This is not really the nicest way to get `InferenceVar`s. Can we get them
379+
// without directly constructing them from `index`?
380+
let var = InferenceVar::from(index as u32).to_ty(Interner, kind);
381+
Some(var)
382+
})
383+
.collect();
384+
385+
for var in scalar_vars {
386+
let maybe_resolved = self.resolve_ty_shallow(&var);
387+
if let TyKind::InferenceVar(_, kind) = maybe_resolved.kind(Interner) {
388+
let fallback = match kind {
389+
TyVariableKind::Integer => &int_fallback,
390+
TyVariableKind::Float => &float_fallback,
391+
TyVariableKind::General => unreachable!(),
392+
};
393+
self.unify(&var, fallback);
394+
}
395+
}
396+
}
397+
343398
/// Unify two relatable values (e.g. `Ty`) and register new trait goals that arise from that.
344399
pub(crate) fn unify<T: ?Sized + Zip<Interner>>(&mut self, ty1: &T, ty2: &T) -> bool {
345400
let result = match self.try_unify(ty1, ty2) {

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

+65
Original file line numberDiff line numberDiff line change
@@ -4100,3 +4100,68 @@ where
41004100
"#,
41014101
);
41024102
}
4103+
4104+
#[test]
4105+
fn bin_op_with_scalar_fallback() {
4106+
// Extra impls are significant so that chalk doesn't give us definite guidances.
4107+
check_types(
4108+
r#"
4109+
//- minicore: add
4110+
use core::ops::Add;
4111+
4112+
struct Vec2<T>(T, T);
4113+
4114+
impl Add for Vec2<i32> {
4115+
type Output = Self;
4116+
fn add(self, rhs: Self) -> Self::Output { loop {} }
4117+
}
4118+
impl Add for Vec2<u32> {
4119+
type Output = Self;
4120+
fn add(self, rhs: Self) -> Self::Output { loop {} }
4121+
}
4122+
impl Add for Vec2<f32> {
4123+
type Output = Self;
4124+
fn add(self, rhs: Self) -> Self::Output { loop {} }
4125+
}
4126+
impl Add for Vec2<f64> {
4127+
type Output = Self;
4128+
fn add(self, rhs: Self) -> Self::Output { loop {} }
4129+
}
4130+
4131+
fn test() {
4132+
let a = Vec2(1, 2);
4133+
let b = Vec2(3, 4);
4134+
let c = a + b;
4135+
//^ Vec2<i32>
4136+
let a = Vec2(1., 2.);
4137+
let b = Vec2(3., 4.);
4138+
let c = a + b;
4139+
//^ Vec2<f64>
4140+
}
4141+
"#,
4142+
);
4143+
}
4144+
4145+
#[test]
4146+
fn trait_method_with_scalar_fallback() {
4147+
check_types(
4148+
r#"
4149+
trait Trait {
4150+
type Output;
4151+
fn foo(&self) -> Self::Output;
4152+
}
4153+
impl<T> Trait for T {
4154+
type Output = T;
4155+
fn foo(&self) -> Self::Output { loop {} }
4156+
}
4157+
fn test() {
4158+
let a = 42;
4159+
let b = a.foo();
4160+
//^ i32
4161+
let a = 3.14;
4162+
let b = a.foo();
4163+
//^ f64
4164+
}
4165+
"#,
4166+
);
4167+
}

0 commit comments

Comments
 (0)