Auto merge of #16749 - Veykril:on-demand-validation-err, r=Veykril
internal: Some method resolution cleanups
This commit is contained in:
commit
a5035f4931
@ -105,6 +105,10 @@ anyhow = "1.0.75"
|
|||||||
arrayvec = "0.7.4"
|
arrayvec = "0.7.4"
|
||||||
bitflags = "2.4.1"
|
bitflags = "2.4.1"
|
||||||
cargo_metadata = "0.18.1"
|
cargo_metadata = "0.18.1"
|
||||||
|
chalk-solve = { version = "0.96.0", default-features = false }
|
||||||
|
chalk-ir = "0.96.0"
|
||||||
|
chalk-recursive = { version = "0.96.0", default-features = false }
|
||||||
|
chalk-derive = "0.96.0"
|
||||||
command-group = "2.0.1"
|
command-group = "2.0.1"
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.8"
|
||||||
dissimilar = "1.0.7"
|
dissimilar = "1.0.7"
|
||||||
|
@ -23,10 +23,10 @@ oorandom = "11.1.3"
|
|||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
rustc-hash.workspace = true
|
rustc-hash.workspace = true
|
||||||
scoped-tls = "1.0.0"
|
scoped-tls = "1.0.0"
|
||||||
chalk-solve = { version = "0.96.0", default-features = false }
|
chalk-solve.workspace = true
|
||||||
chalk-ir = "0.96.0"
|
chalk-ir.workspace = true
|
||||||
chalk-recursive = { version = "0.96.0", default-features = false }
|
chalk-recursive.workspace = true
|
||||||
chalk-derive = "0.96.0"
|
chalk-derive.workspace = true
|
||||||
la-arena.workspace = true
|
la-arena.workspace = true
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
triomphe.workspace = true
|
triomphe.workspace = true
|
||||||
|
@ -113,7 +113,7 @@ pub(crate) fn autoderef_step(
|
|||||||
ty: Ty,
|
ty: Ty,
|
||||||
explicit: bool,
|
explicit: bool,
|
||||||
) -> Option<(AutoderefKind, Ty)> {
|
) -> Option<(AutoderefKind, Ty)> {
|
||||||
if let Some(derefed) = builtin_deref(table, &ty, explicit) {
|
if let Some(derefed) = builtin_deref(table.db, &ty, explicit) {
|
||||||
Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed)))
|
Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed)))
|
||||||
} else {
|
} else {
|
||||||
Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?))
|
Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?))
|
||||||
@ -121,7 +121,7 @@ pub(crate) fn autoderef_step(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn builtin_deref<'ty>(
|
pub(crate) fn builtin_deref<'ty>(
|
||||||
table: &mut InferenceTable<'_>,
|
db: &dyn HirDatabase,
|
||||||
ty: &'ty Ty,
|
ty: &'ty Ty,
|
||||||
explicit: bool,
|
explicit: bool,
|
||||||
) -> Option<&'ty Ty> {
|
) -> Option<&'ty Ty> {
|
||||||
@ -129,7 +129,7 @@ pub(crate) fn builtin_deref<'ty>(
|
|||||||
TyKind::Ref(.., ty) => Some(ty),
|
TyKind::Ref(.., ty) => Some(ty),
|
||||||
TyKind::Raw(.., ty) if explicit => Some(ty),
|
TyKind::Raw(.., ty) if explicit => Some(ty),
|
||||||
&TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => {
|
&TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => {
|
||||||
if crate::lang_items::is_box(table.db, adt) {
|
if crate::lang_items::is_box(db, adt) {
|
||||||
substs.at(Interner, 0).ty(Interner)
|
substs.at(Interner, 0).ty(Interner)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -647,7 +647,7 @@ fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> CoerceResult {
|
|||||||
let goal: InEnvironment<DomainGoal> =
|
let goal: InEnvironment<DomainGoal> =
|
||||||
InEnvironment::new(&self.trait_env.env, coerce_unsized_tref.cast(Interner));
|
InEnvironment::new(&self.trait_env.env, coerce_unsized_tref.cast(Interner));
|
||||||
|
|
||||||
let canonicalized = self.canonicalize(goal);
|
let canonicalized = self.canonicalize_with_free_vars(goal);
|
||||||
|
|
||||||
// FIXME: rustc's coerce_unsized is more specialized -- it only tries to
|
// FIXME: rustc's coerce_unsized is more specialized -- it only tries to
|
||||||
// solve `CoerceUnsized` and `Unsize` goals at this point and leaves the
|
// solve `CoerceUnsized` and `Unsize` goals at this point and leaves the
|
||||||
|
@ -312,15 +312,13 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
Expr::Call { callee, args, .. } => {
|
Expr::Call { callee, args, .. } => {
|
||||||
let callee_ty = self.infer_expr(*callee, &Expectation::none());
|
let callee_ty = self.infer_expr(*callee, &Expectation::none());
|
||||||
let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false);
|
let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false);
|
||||||
let (res, derefed_callee) = 'b: {
|
let (res, derefed_callee) = loop {
|
||||||
// manual loop to be able to access `derefs.table`
|
let Some((callee_deref_ty, _)) = derefs.next() else {
|
||||||
while let Some((callee_deref_ty, _)) = derefs.next() {
|
break (None, callee_ty.clone());
|
||||||
let res = derefs.table.callable_sig(&callee_deref_ty, args.len());
|
};
|
||||||
if res.is_some() {
|
if let Some(res) = derefs.table.callable_sig(&callee_deref_ty, args.len()) {
|
||||||
break 'b (res, callee_deref_ty);
|
break (Some(res), callee_deref_ty);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(None, callee_ty.clone())
|
|
||||||
};
|
};
|
||||||
// if the function is unresolved, we use is_varargs=true to
|
// if the function is unresolved, we use is_varargs=true to
|
||||||
// suppress the arg count diagnostic here
|
// suppress the arg count diagnostic here
|
||||||
@ -657,7 +655,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(derefed) = builtin_deref(&mut self.table, &inner_ty, true) {
|
if let Some(derefed) = builtin_deref(self.table.db, &inner_ty, true) {
|
||||||
self.resolve_ty_shallow(derefed)
|
self.resolve_ty_shallow(derefed)
|
||||||
} else {
|
} else {
|
||||||
deref_by_trait(&mut self.table, inner_ty)
|
deref_by_trait(&mut self.table, inner_ty)
|
||||||
@ -774,7 +772,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
let receiver_adjustments = method_resolution::resolve_indexing_op(
|
let receiver_adjustments = method_resolution::resolve_indexing_op(
|
||||||
self.db,
|
self.db,
|
||||||
self.table.trait_env.clone(),
|
self.table.trait_env.clone(),
|
||||||
canonicalized.value,
|
canonicalized,
|
||||||
index_trait,
|
index_trait,
|
||||||
);
|
);
|
||||||
let (self_ty, mut adj) = receiver_adjustments
|
let (self_ty, mut adj) = receiver_adjustments
|
||||||
@ -1559,7 +1557,7 @@ fn infer_field_access(
|
|||||||
let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
|
let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
|
||||||
let resolved = method_resolution::lookup_method(
|
let resolved = method_resolution::lookup_method(
|
||||||
self.db,
|
self.db,
|
||||||
&canonicalized_receiver.value,
|
&canonicalized_receiver,
|
||||||
self.table.trait_env.clone(),
|
self.table.trait_env.clone(),
|
||||||
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||||
VisibleFromModule::Filter(self.resolver.module()),
|
VisibleFromModule::Filter(self.resolver.module()),
|
||||||
@ -1608,7 +1606,7 @@ fn infer_method_call(
|
|||||||
|
|
||||||
let resolved = method_resolution::lookup_method(
|
let resolved = method_resolution::lookup_method(
|
||||||
self.db,
|
self.db,
|
||||||
&canonicalized_receiver.value,
|
&canonicalized_receiver,
|
||||||
self.table.trait_env.clone(),
|
self.table.trait_env.clone(),
|
||||||
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||||
VisibleFromModule::Filter(self.resolver.module()),
|
VisibleFromModule::Filter(self.resolver.module()),
|
||||||
@ -1641,7 +1639,7 @@ fn infer_method_call(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let assoc_func_with_same_name = method_resolution::iterate_method_candidates(
|
let assoc_func_with_same_name = method_resolution::iterate_method_candidates(
|
||||||
&canonicalized_receiver.value,
|
&canonicalized_receiver,
|
||||||
self.db,
|
self.db,
|
||||||
self.table.trait_env.clone(),
|
self.table.trait_env.clone(),
|
||||||
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||||
|
@ -321,7 +321,7 @@ fn resolve_ty_assoc_item(
|
|||||||
|
|
||||||
let mut not_visible = None;
|
let mut not_visible = None;
|
||||||
let res = method_resolution::iterate_method_candidates(
|
let res = method_resolution::iterate_method_candidates(
|
||||||
&canonical_ty.value,
|
&canonical_ty,
|
||||||
self.db,
|
self.db,
|
||||||
self.table.trait_env.clone(),
|
self.table.trait_env.clone(),
|
||||||
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||||
|
@ -23,12 +23,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
impl InferenceContext<'_> {
|
impl InferenceContext<'_> {
|
||||||
pub(super) fn canonicalize<T: TypeFoldable<Interner> + HasInterner<Interner = Interner>>(
|
pub(super) fn canonicalize<T>(&mut self, t: T) -> Canonical<T>
|
||||||
&mut self,
|
|
||||||
t: T,
|
|
||||||
) -> Canonicalized<T>
|
|
||||||
where
|
where
|
||||||
T: HasInterner<Interner = Interner>,
|
T: TypeFoldable<Interner> + HasInterner<Interner = Interner>,
|
||||||
{
|
{
|
||||||
self.table.canonicalize(t)
|
self.table.canonicalize(t)
|
||||||
}
|
}
|
||||||
@ -128,14 +125,14 @@ pub(crate) fn apply_solution(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
for (i, v) in solution.value.iter(Interner).enumerate() {
|
for (i, v) in solution.value.iter(Interner).enumerate() {
|
||||||
let var = self.free_vars[i].clone();
|
let var = &self.free_vars[i];
|
||||||
if let Some(ty) = v.ty(Interner) {
|
if let Some(ty) = v.ty(Interner) {
|
||||||
// eagerly replace projections in the type; we may be getting types
|
// eagerly replace projections in the type; we may be getting types
|
||||||
// e.g. from where clauses where this hasn't happened yet
|
// e.g. from where clauses where this hasn't happened yet
|
||||||
let ty = ctx.normalize_associated_types_in(new_vars.apply(ty.clone(), Interner));
|
let ty = ctx.normalize_associated_types_in(new_vars.apply(ty.clone(), Interner));
|
||||||
ctx.unify(var.assert_ty_ref(Interner), &ty);
|
ctx.unify(var.assert_ty_ref(Interner), &ty);
|
||||||
} else {
|
} else {
|
||||||
let _ = ctx.try_unify(&var, &new_vars.apply(v.clone(), Interner));
|
let _ = ctx.try_unify(var, &new_vars.apply(v.clone(), Interner));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,7 +240,7 @@ pub(crate) struct InferenceTable<'a> {
|
|||||||
pub(crate) db: &'a dyn HirDatabase,
|
pub(crate) db: &'a dyn HirDatabase,
|
||||||
pub(crate) trait_env: Arc<TraitEnvironment>,
|
pub(crate) trait_env: Arc<TraitEnvironment>,
|
||||||
var_unification_table: ChalkInferenceTable,
|
var_unification_table: ChalkInferenceTable,
|
||||||
type_variable_table: Vec<TypeVariableFlags>,
|
type_variable_table: SmallVec<[TypeVariableFlags; 16]>,
|
||||||
pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
|
pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
|
||||||
/// Double buffer used in [`Self::resolve_obligations_as_possible`] to cut down on
|
/// Double buffer used in [`Self::resolve_obligations_as_possible`] to cut down on
|
||||||
/// temporary allocations.
|
/// temporary allocations.
|
||||||
@ -252,8 +249,8 @@ pub(crate) struct InferenceTable<'a> {
|
|||||||
|
|
||||||
pub(crate) struct InferenceTableSnapshot {
|
pub(crate) struct InferenceTableSnapshot {
|
||||||
var_table_snapshot: chalk_solve::infer::InferenceSnapshot<Interner>,
|
var_table_snapshot: chalk_solve::infer::InferenceSnapshot<Interner>,
|
||||||
|
type_variable_table: SmallVec<[TypeVariableFlags; 16]>,
|
||||||
pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
|
pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
|
||||||
type_variable_table_snapshot: Vec<TypeVariableFlags>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InferenceTable<'a> {
|
impl<'a> InferenceTable<'a> {
|
||||||
@ -262,7 +259,7 @@ pub(crate) fn new(db: &'a dyn HirDatabase, trait_env: Arc<TraitEnvironment>) ->
|
|||||||
db,
|
db,
|
||||||
trait_env,
|
trait_env,
|
||||||
var_unification_table: ChalkInferenceTable::new(),
|
var_unification_table: ChalkInferenceTable::new(),
|
||||||
type_variable_table: Vec::new(),
|
type_variable_table: SmallVec::new(),
|
||||||
pending_obligations: Vec::new(),
|
pending_obligations: Vec::new(),
|
||||||
resolve_obligations_buffer: Vec::new(),
|
resolve_obligations_buffer: Vec::new(),
|
||||||
}
|
}
|
||||||
@ -292,14 +289,14 @@ pub(super) fn set_diverging(&mut self, iv: InferenceVar, diverging: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fallback_value(&self, iv: InferenceVar, kind: TyVariableKind) -> Ty {
|
fn fallback_value(&self, iv: InferenceVar, kind: TyVariableKind) -> Ty {
|
||||||
|
let is_diverging = self
|
||||||
|
.type_variable_table
|
||||||
|
.get(iv.index() as usize)
|
||||||
|
.map_or(false, |data| data.contains(TypeVariableFlags::DIVERGING));
|
||||||
|
if is_diverging {
|
||||||
|
return TyKind::Never.intern(Interner);
|
||||||
|
}
|
||||||
match kind {
|
match kind {
|
||||||
_ if self
|
|
||||||
.type_variable_table
|
|
||||||
.get(iv.index() as usize)
|
|
||||||
.map_or(false, |data| data.contains(TypeVariableFlags::DIVERGING)) =>
|
|
||||||
{
|
|
||||||
TyKind::Never
|
|
||||||
}
|
|
||||||
TyVariableKind::General => TyKind::Error,
|
TyVariableKind::General => TyKind::Error,
|
||||||
TyVariableKind::Integer => TyKind::Scalar(Scalar::Int(IntTy::I32)),
|
TyVariableKind::Integer => TyKind::Scalar(Scalar::Int(IntTy::I32)),
|
||||||
TyVariableKind::Float => TyKind::Scalar(Scalar::Float(FloatTy::F64)),
|
TyVariableKind::Float => TyKind::Scalar(Scalar::Float(FloatTy::F64)),
|
||||||
@ -307,12 +304,9 @@ fn fallback_value(&self, iv: InferenceVar, kind: TyVariableKind) -> Ty {
|
|||||||
.intern(Interner)
|
.intern(Interner)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn canonicalize<T: TypeFoldable<Interner> + HasInterner<Interner = Interner>>(
|
pub(crate) fn canonicalize_with_free_vars<T>(&mut self, t: T) -> Canonicalized<T>
|
||||||
&mut self,
|
|
||||||
t: T,
|
|
||||||
) -> Canonicalized<T>
|
|
||||||
where
|
where
|
||||||
T: HasInterner<Interner = Interner>,
|
T: TypeFoldable<Interner> + HasInterner<Interner = Interner>,
|
||||||
{
|
{
|
||||||
// try to resolve obligations before canonicalizing, since this might
|
// try to resolve obligations before canonicalizing, since this might
|
||||||
// result in new knowledge about variables
|
// result in new knowledge about variables
|
||||||
@ -326,6 +320,16 @@ pub(crate) fn canonicalize<T: TypeFoldable<Interner> + HasInterner<Interner = In
|
|||||||
Canonicalized { value: result.quantified, free_vars }
|
Canonicalized { value: result.quantified, free_vars }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn canonicalize<T>(&mut self, t: T) -> Canonical<T>
|
||||||
|
where
|
||||||
|
T: TypeFoldable<Interner> + HasInterner<Interner = Interner>,
|
||||||
|
{
|
||||||
|
// try to resolve obligations before canonicalizing, since this might
|
||||||
|
// result in new knowledge about variables
|
||||||
|
self.resolve_obligations_as_possible();
|
||||||
|
self.var_unification_table.canonicalize(Interner, t).quantified
|
||||||
|
}
|
||||||
|
|
||||||
/// Recurses through the given type, normalizing associated types mentioned
|
/// Recurses through the given type, normalizing associated types mentioned
|
||||||
/// in it by replacing them by type variables and registering obligations to
|
/// in it by replacing them by type variables and registering obligations to
|
||||||
/// resolve later. This should be done once for every type we get from some
|
/// resolve later. This should be done once for every type we get from some
|
||||||
@ -434,6 +438,7 @@ pub(crate) fn resolve_with_fallback<T>(
|
|||||||
where
|
where
|
||||||
T: HasInterner<Interner = Interner> + TypeFoldable<Interner>,
|
T: HasInterner<Interner = Interner> + TypeFoldable<Interner>,
|
||||||
{
|
{
|
||||||
|
// TODO check this vec here
|
||||||
self.resolve_with_fallback_inner(&mut Vec::new(), t, &fallback)
|
self.resolve_with_fallback_inner(&mut Vec::new(), t, &fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,7 +546,7 @@ pub(crate) fn unify_deeply<T: ?Sized + Zip<Interner>>(&mut self, ty1: &T, ty2: &
|
|||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
result.goals.iter().all(|goal| {
|
result.goals.iter().all(|goal| {
|
||||||
let canonicalized = self.canonicalize(goal.clone());
|
let canonicalized = self.canonicalize_with_free_vars(goal.clone());
|
||||||
self.try_resolve_obligation(&canonicalized).is_some()
|
self.try_resolve_obligation(&canonicalized).is_some()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -575,19 +580,15 @@ pub(crate) fn resolve_ty_shallow(&mut self, ty: &Ty) -> Ty {
|
|||||||
|
|
||||||
pub(crate) fn snapshot(&mut self) -> InferenceTableSnapshot {
|
pub(crate) fn snapshot(&mut self) -> InferenceTableSnapshot {
|
||||||
let var_table_snapshot = self.var_unification_table.snapshot();
|
let var_table_snapshot = self.var_unification_table.snapshot();
|
||||||
let type_variable_table_snapshot = self.type_variable_table.clone();
|
let type_variable_table = self.type_variable_table.clone();
|
||||||
let pending_obligations = self.pending_obligations.clone();
|
let pending_obligations = self.pending_obligations.clone();
|
||||||
InferenceTableSnapshot {
|
InferenceTableSnapshot { var_table_snapshot, pending_obligations, type_variable_table }
|
||||||
var_table_snapshot,
|
|
||||||
pending_obligations,
|
|
||||||
type_variable_table_snapshot,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub(crate) fn rollback_to(&mut self, snapshot: InferenceTableSnapshot) {
|
pub(crate) fn rollback_to(&mut self, snapshot: InferenceTableSnapshot) {
|
||||||
self.var_unification_table.rollback_to(snapshot.var_table_snapshot);
|
self.var_unification_table.rollback_to(snapshot.var_table_snapshot);
|
||||||
self.type_variable_table = snapshot.type_variable_table_snapshot;
|
self.type_variable_table = snapshot.type_variable_table;
|
||||||
self.pending_obligations = snapshot.pending_obligations;
|
self.pending_obligations = snapshot.pending_obligations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,7 +607,7 @@ pub(crate) fn try_obligation(&mut self, goal: Goal) -> Option<Solution> {
|
|||||||
let in_env = InEnvironment::new(&self.trait_env.env, goal);
|
let in_env = InEnvironment::new(&self.trait_env.env, goal);
|
||||||
let canonicalized = self.canonicalize(in_env);
|
let canonicalized = self.canonicalize(in_env);
|
||||||
|
|
||||||
self.db.trait_solve(self.trait_env.krate, self.trait_env.block, canonicalized.value)
|
self.db.trait_solve(self.trait_env.krate, self.trait_env.block, canonicalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn register_obligation(&mut self, goal: Goal) {
|
pub(crate) fn register_obligation(&mut self, goal: Goal) {
|
||||||
@ -615,7 +616,7 @@ pub(crate) fn register_obligation(&mut self, goal: Goal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn register_obligation_in_env(&mut self, goal: InEnvironment<Goal>) {
|
fn register_obligation_in_env(&mut self, goal: InEnvironment<Goal>) {
|
||||||
let canonicalized = self.canonicalize(goal);
|
let canonicalized = self.canonicalize_with_free_vars(goal);
|
||||||
let solution = self.try_resolve_obligation(&canonicalized);
|
let solution = self.try_resolve_obligation(&canonicalized);
|
||||||
if matches!(solution, Some(Solution::Ambig(_))) {
|
if matches!(solution, Some(Solution::Ambig(_))) {
|
||||||
self.pending_obligations.push(canonicalized);
|
self.pending_obligations.push(canonicalized);
|
||||||
@ -798,7 +799,7 @@ fn callable_sig_from_fn_trait(
|
|||||||
let trait_data = self.db.trait_data(fn_once_trait);
|
let trait_data = self.db.trait_data(fn_once_trait);
|
||||||
let output_assoc_type = trait_data.associated_type_by_name(&name![Output])?;
|
let output_assoc_type = trait_data.associated_type_by_name(&name![Output])?;
|
||||||
|
|
||||||
let mut arg_tys = vec![];
|
let mut arg_tys = Vec::with_capacity(num_args);
|
||||||
let arg_ty = TyBuilder::tuple(num_args)
|
let arg_ty = TyBuilder::tuple(num_args)
|
||||||
.fill(|it| {
|
.fill(|it| {
|
||||||
let arg = match it {
|
let arg = match it {
|
||||||
@ -828,11 +829,7 @@ fn callable_sig_from_fn_trait(
|
|||||||
environment: trait_env.clone(),
|
environment: trait_env.clone(),
|
||||||
};
|
};
|
||||||
let canonical = self.canonicalize(obligation.clone());
|
let canonical = self.canonicalize(obligation.clone());
|
||||||
if self
|
if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() {
|
||||||
.db
|
|
||||||
.trait_solve(krate, self.trait_env.block, canonical.value.cast(Interner))
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
self.register_obligation(obligation.goal);
|
self.register_obligation(obligation.goal);
|
||||||
let return_ty = self.normalize_projection_ty(projection);
|
let return_ty = self.normalize_projection_ty(projection);
|
||||||
for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] {
|
for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] {
|
||||||
@ -845,7 +842,7 @@ fn callable_sig_from_fn_trait(
|
|||||||
let canonical = self.canonicalize(obligation.clone());
|
let canonical = self.canonicalize(obligation.clone());
|
||||||
if self
|
if self
|
||||||
.db
|
.db
|
||||||
.trait_solve(krate, self.trait_env.block, canonical.value.cast(Interner))
|
.trait_solve(krate, self.trait_env.block, canonical.cast(Interner))
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return Some((fn_x, arg_tys, return_ty));
|
return Some((fn_x, arg_tys, return_ty));
|
||||||
|
@ -972,10 +972,9 @@ pub fn iterate_method_candidates_dyn(
|
|||||||
|
|
||||||
deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| {
|
deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| {
|
||||||
iterate_method_candidates_with_autoref(
|
iterate_method_candidates_with_autoref(
|
||||||
&receiver_ty,
|
&mut table,
|
||||||
|
receiver_ty,
|
||||||
adj,
|
adj,
|
||||||
db,
|
|
||||||
env.clone(),
|
|
||||||
traits_in_scope,
|
traits_in_scope,
|
||||||
visible_from_module,
|
visible_from_module,
|
||||||
name,
|
name,
|
||||||
@ -1000,10 +999,9 @@ pub fn iterate_method_candidates_dyn(
|
|||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(name = ?name))]
|
#[tracing::instrument(skip_all, fields(name = ?name))]
|
||||||
fn iterate_method_candidates_with_autoref(
|
fn iterate_method_candidates_with_autoref(
|
||||||
receiver_ty: &Canonical<Ty>,
|
table: &mut InferenceTable<'_>,
|
||||||
|
receiver_ty: Canonical<Ty>,
|
||||||
first_adjustment: ReceiverAdjustments,
|
first_adjustment: ReceiverAdjustments,
|
||||||
db: &dyn HirDatabase,
|
|
||||||
env: Arc<TraitEnvironment>,
|
|
||||||
traits_in_scope: &FxHashSet<TraitId>,
|
traits_in_scope: &FxHashSet<TraitId>,
|
||||||
visible_from_module: VisibleFromModule,
|
visible_from_module: VisibleFromModule,
|
||||||
name: Option<&Name>,
|
name: Option<&Name>,
|
||||||
@ -1016,10 +1014,9 @@ fn iterate_method_candidates_with_autoref(
|
|||||||
|
|
||||||
let mut iterate_method_candidates_by_receiver = move |receiver_ty, first_adjustment| {
|
let mut iterate_method_candidates_by_receiver = move |receiver_ty, first_adjustment| {
|
||||||
iterate_method_candidates_by_receiver(
|
iterate_method_candidates_by_receiver(
|
||||||
|
table,
|
||||||
receiver_ty,
|
receiver_ty,
|
||||||
first_adjustment,
|
first_adjustment,
|
||||||
db,
|
|
||||||
env.clone(),
|
|
||||||
traits_in_scope,
|
traits_in_scope,
|
||||||
visible_from_module,
|
visible_from_module,
|
||||||
name,
|
name,
|
||||||
@ -1034,7 +1031,7 @@ fn iterate_method_candidates_with_autoref(
|
|||||||
maybe_reborrowed.autoderefs += 1;
|
maybe_reborrowed.autoderefs += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterate_method_candidates_by_receiver(receiver_ty, maybe_reborrowed)?;
|
iterate_method_candidates_by_receiver(receiver_ty.clone(), maybe_reborrowed)?;
|
||||||
|
|
||||||
let refed = Canonical {
|
let refed = Canonical {
|
||||||
value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone())
|
value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone())
|
||||||
@ -1042,7 +1039,7 @@ fn iterate_method_candidates_with_autoref(
|
|||||||
binders: receiver_ty.binders.clone(),
|
binders: receiver_ty.binders.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
iterate_method_candidates_by_receiver(&refed, first_adjustment.with_autoref(Mutability::Not))?;
|
iterate_method_candidates_by_receiver(refed, first_adjustment.with_autoref(Mutability::Not))?;
|
||||||
|
|
||||||
let ref_muted = Canonical {
|
let ref_muted = Canonical {
|
||||||
value: TyKind::Ref(Mutability::Mut, static_lifetime(), receiver_ty.value.clone())
|
value: TyKind::Ref(Mutability::Mut, static_lifetime(), receiver_ty.value.clone())
|
||||||
@ -1050,58 +1047,54 @@ fn iterate_method_candidates_with_autoref(
|
|||||||
binders: receiver_ty.binders.clone(),
|
binders: receiver_ty.binders.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
iterate_method_candidates_by_receiver(
|
iterate_method_candidates_by_receiver(ref_muted, first_adjustment.with_autoref(Mutability::Mut))
|
||||||
&ref_muted,
|
|
||||||
first_adjustment.with_autoref(Mutability::Mut),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(name = ?name))]
|
#[tracing::instrument(skip_all, fields(name = ?name))]
|
||||||
fn iterate_method_candidates_by_receiver(
|
fn iterate_method_candidates_by_receiver(
|
||||||
receiver_ty: &Canonical<Ty>,
|
table: &mut InferenceTable<'_>,
|
||||||
|
receiver_ty: Canonical<Ty>,
|
||||||
receiver_adjustments: ReceiverAdjustments,
|
receiver_adjustments: ReceiverAdjustments,
|
||||||
db: &dyn HirDatabase,
|
|
||||||
env: Arc<TraitEnvironment>,
|
|
||||||
traits_in_scope: &FxHashSet<TraitId>,
|
traits_in_scope: &FxHashSet<TraitId>,
|
||||||
visible_from_module: VisibleFromModule,
|
visible_from_module: VisibleFromModule,
|
||||||
name: Option<&Name>,
|
name: Option<&Name>,
|
||||||
mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
||||||
) -> ControlFlow<()> {
|
) -> ControlFlow<()> {
|
||||||
let mut table = InferenceTable::new(db, env);
|
table.run_in_snapshot(|table| {
|
||||||
let receiver_ty = table.instantiate_canonical(receiver_ty.clone());
|
let receiver_ty = table.instantiate_canonical(receiver_ty.clone());
|
||||||
let snapshot = table.snapshot();
|
// We're looking for methods with *receiver* type receiver_ty. These could
|
||||||
// We're looking for methods with *receiver* type receiver_ty. These could
|
// be found in any of the derefs of receiver_ty, so we have to go through
|
||||||
// be found in any of the derefs of receiver_ty, so we have to go through
|
// that, including raw derefs.
|
||||||
// that, including raw derefs.
|
table.run_in_snapshot(|table| {
|
||||||
let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true);
|
let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true);
|
||||||
while let Some((self_ty, _)) = autoderef.next() {
|
while let Some((self_ty, _)) = autoderef.next() {
|
||||||
iterate_inherent_methods(
|
iterate_inherent_methods(
|
||||||
&self_ty,
|
&self_ty,
|
||||||
autoderef.table,
|
autoderef.table,
|
||||||
name,
|
name,
|
||||||
Some(&receiver_ty),
|
Some(&receiver_ty),
|
||||||
Some(receiver_adjustments.clone()),
|
Some(receiver_adjustments.clone()),
|
||||||
visible_from_module,
|
visible_from_module,
|
||||||
&mut callback,
|
&mut callback,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
})?;
|
||||||
|
|
||||||
table.rollback_to(snapshot);
|
let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true);
|
||||||
|
while let Some((self_ty, _)) = autoderef.next() {
|
||||||
let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true);
|
iterate_trait_method_candidates(
|
||||||
while let Some((self_ty, _)) = autoderef.next() {
|
&self_ty,
|
||||||
iterate_trait_method_candidates(
|
autoderef.table,
|
||||||
&self_ty,
|
traits_in_scope,
|
||||||
autoderef.table,
|
name,
|
||||||
traits_in_scope,
|
Some(&receiver_ty),
|
||||||
name,
|
Some(receiver_adjustments.clone()),
|
||||||
Some(&receiver_ty),
|
&mut callback,
|
||||||
Some(receiver_adjustments.clone()),
|
)?
|
||||||
&mut callback,
|
}
|
||||||
)?
|
ControlFlow::Continue(())
|
||||||
}
|
})
|
||||||
|
|
||||||
ControlFlow::Continue(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(name = ?name))]
|
#[tracing::instrument(skip_all, fields(name = ?name))]
|
||||||
@ -1147,9 +1140,9 @@ fn iterate_trait_method_candidates(
|
|||||||
callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
||||||
) -> ControlFlow<()> {
|
) -> ControlFlow<()> {
|
||||||
let db = table.db;
|
let db = table.db;
|
||||||
let env = table.trait_env.clone();
|
|
||||||
|
|
||||||
let canonical_self_ty = table.canonicalize(self_ty.clone()).value;
|
let canonical_self_ty = table.canonicalize(self_ty.clone());
|
||||||
|
let TraitEnvironment { krate, block, .. } = *table.trait_env;
|
||||||
|
|
||||||
'traits: for &t in traits_in_scope {
|
'traits: for &t in traits_in_scope {
|
||||||
let data = db.trait_data(t);
|
let data = db.trait_data(t);
|
||||||
@ -1164,7 +1157,7 @@ fn iterate_trait_method_candidates(
|
|||||||
{
|
{
|
||||||
// FIXME: this should really be using the edition of the method name's span, in case it
|
// FIXME: this should really be using the edition of the method name's span, in case it
|
||||||
// comes from a macro
|
// comes from a macro
|
||||||
if db.crate_graph()[env.krate].edition < Edition::Edition2021 {
|
if db.crate_graph()[krate].edition < Edition::Edition2021 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1183,8 +1176,8 @@ fn iterate_trait_method_candidates(
|
|||||||
IsValidCandidate::No => continue,
|
IsValidCandidate::No => continue,
|
||||||
};
|
};
|
||||||
if !known_implemented {
|
if !known_implemented {
|
||||||
let goal = generic_implements_goal(db, env.clone(), t, &canonical_self_ty);
|
let goal = generic_implements_goal(db, &table.trait_env, t, &canonical_self_ty);
|
||||||
if db.trait_solve(env.krate, env.block, goal.cast(Interner)).is_none() {
|
if db.trait_solve(krate, block, goal.cast(Interner)).is_none() {
|
||||||
continue 'traits;
|
continue 'traits;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1365,7 +1358,7 @@ pub(crate) fn resolve_indexing_op(
|
|||||||
let ty = table.instantiate_canonical(ty);
|
let ty = table.instantiate_canonical(ty);
|
||||||
let deref_chain = autoderef_method_receiver(&mut table, ty);
|
let deref_chain = autoderef_method_receiver(&mut table, ty);
|
||||||
for (ty, adj) in deref_chain {
|
for (ty, adj) in deref_chain {
|
||||||
let goal = generic_implements_goal(db, table.trait_env.clone(), index_trait, &ty);
|
let goal = generic_implements_goal(db, &table.trait_env, index_trait, &ty);
|
||||||
if db
|
if db
|
||||||
.trait_solve(table.trait_env.krate, table.trait_env.block, goal.cast(Interner))
|
.trait_solve(table.trait_env.krate, table.trait_env.block, goal.cast(Interner))
|
||||||
.is_some()
|
.is_some()
|
||||||
@ -1548,7 +1541,7 @@ fn is_valid_impl_fn_candidate(
|
|||||||
|
|
||||||
for goal in goals.clone() {
|
for goal in goals.clone() {
|
||||||
let in_env = InEnvironment::new(&table.trait_env.env, goal);
|
let in_env = InEnvironment::new(&table.trait_env.env, goal);
|
||||||
let canonicalized = table.canonicalize(in_env);
|
let canonicalized = table.canonicalize_with_free_vars(in_env);
|
||||||
let solution = table.db.trait_solve(
|
let solution = table.db.trait_solve(
|
||||||
table.trait_env.krate,
|
table.trait_env.krate,
|
||||||
table.trait_env.block,
|
table.trait_env.block,
|
||||||
@ -1586,10 +1579,10 @@ fn is_valid_impl_fn_candidate(
|
|||||||
pub fn implements_trait(
|
pub fn implements_trait(
|
||||||
ty: &Canonical<Ty>,
|
ty: &Canonical<Ty>,
|
||||||
db: &dyn HirDatabase,
|
db: &dyn HirDatabase,
|
||||||
env: Arc<TraitEnvironment>,
|
env: &TraitEnvironment,
|
||||||
trait_: TraitId,
|
trait_: TraitId,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let goal = generic_implements_goal(db, env.clone(), trait_, ty);
|
let goal = generic_implements_goal(db, env, trait_, ty);
|
||||||
let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner));
|
let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner));
|
||||||
|
|
||||||
solution.is_some()
|
solution.is_some()
|
||||||
@ -1598,10 +1591,10 @@ pub fn implements_trait(
|
|||||||
pub fn implements_trait_unique(
|
pub fn implements_trait_unique(
|
||||||
ty: &Canonical<Ty>,
|
ty: &Canonical<Ty>,
|
||||||
db: &dyn HirDatabase,
|
db: &dyn HirDatabase,
|
||||||
env: Arc<TraitEnvironment>,
|
env: &TraitEnvironment,
|
||||||
trait_: TraitId,
|
trait_: TraitId,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let goal = generic_implements_goal(db, env.clone(), trait_, ty);
|
let goal = generic_implements_goal(db, env, trait_, ty);
|
||||||
let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner));
|
let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner));
|
||||||
|
|
||||||
matches!(solution, Some(crate::Solution::Unique(_)))
|
matches!(solution, Some(crate::Solution::Unique(_)))
|
||||||
@ -1612,32 +1605,34 @@ pub fn implements_trait_unique(
|
|||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn generic_implements_goal(
|
fn generic_implements_goal(
|
||||||
db: &dyn HirDatabase,
|
db: &dyn HirDatabase,
|
||||||
env: Arc<TraitEnvironment>,
|
env: &TraitEnvironment,
|
||||||
trait_: TraitId,
|
trait_: TraitId,
|
||||||
self_ty: &Canonical<Ty>,
|
self_ty: &Canonical<Ty>,
|
||||||
) -> Canonical<InEnvironment<super::DomainGoal>> {
|
) -> Canonical<InEnvironment<super::DomainGoal>> {
|
||||||
let mut kinds = self_ty.binders.interned().to_vec();
|
let binders = self_ty.binders.interned();
|
||||||
let trait_ref = TyBuilder::trait_ref(db, trait_)
|
let trait_ref = TyBuilder::trait_ref(db, trait_)
|
||||||
.push(self_ty.value.clone())
|
.push(self_ty.value.clone())
|
||||||
.fill_with_bound_vars(DebruijnIndex::INNERMOST, kinds.len())
|
.fill_with_bound_vars(DebruijnIndex::INNERMOST, binders.len())
|
||||||
.build();
|
.build();
|
||||||
kinds.extend(trait_ref.substitution.iter(Interner).skip(1).map(|it| {
|
|
||||||
let vk = match it.data(Interner) {
|
let kinds =
|
||||||
chalk_ir::GenericArgData::Ty(_) => {
|
binders.iter().cloned().chain(trait_ref.substitution.iter(Interner).skip(1).map(|it| {
|
||||||
chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)
|
let vk = match it.data(Interner) {
|
||||||
}
|
chalk_ir::GenericArgData::Ty(_) => {
|
||||||
chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime,
|
chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)
|
||||||
chalk_ir::GenericArgData::Const(c) => {
|
}
|
||||||
chalk_ir::VariableKind::Const(c.data(Interner).ty.clone())
|
chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime,
|
||||||
}
|
chalk_ir::GenericArgData::Const(c) => {
|
||||||
};
|
chalk_ir::VariableKind::Const(c.data(Interner).ty.clone())
|
||||||
chalk_ir::WithKind::new(vk, UniverseIndex::ROOT)
|
}
|
||||||
}));
|
};
|
||||||
|
chalk_ir::WithKind::new(vk, UniverseIndex::ROOT)
|
||||||
|
}));
|
||||||
|
let binders = CanonicalVarKinds::from_iter(Interner, kinds);
|
||||||
|
|
||||||
let obligation = trait_ref.cast(Interner);
|
let obligation = trait_ref.cast(Interner);
|
||||||
Canonical {
|
let value = InEnvironment::new(&env.env, obligation);
|
||||||
binders: CanonicalVarKinds::from_iter(Interner, kinds),
|
Canonical { binders, value }
|
||||||
value: InEnvironment::new(&env.env, obligation),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autoderef_method_receiver(
|
fn autoderef_method_receiver(
|
||||||
@ -1648,7 +1643,7 @@ fn autoderef_method_receiver(
|
|||||||
let mut autoderef = autoderef::Autoderef::new(table, ty, false);
|
let mut autoderef = autoderef::Autoderef::new(table, ty, false);
|
||||||
while let Some((ty, derefs)) = autoderef.next() {
|
while let Some((ty, derefs)) = autoderef.next() {
|
||||||
deref_chain.push((
|
deref_chain.push((
|
||||||
autoderef.table.canonicalize(ty).value,
|
autoderef.table.canonicalize(ty),
|
||||||
ReceiverAdjustments { autoref: None, autoderefs: derefs, unsize_array: false },
|
ReceiverAdjustments { autoref: None, autoderefs: derefs, unsize_array: false },
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -1773,6 +1773,21 @@ fn test() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deref_into_inference_var() {
|
||||||
|
check_types(
|
||||||
|
r#"
|
||||||
|
//- minicore:deref
|
||||||
|
struct A<T>(T);
|
||||||
|
impl core::ops::Deref for A<u32> {}
|
||||||
|
impl A<i32> { fn foo(&self) {} }
|
||||||
|
fn main() {
|
||||||
|
A(0).foo();
|
||||||
|
//^^^^^^^^^^ ()
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn receiver_adjustment_autoref() {
|
fn receiver_adjustment_autoref() {
|
||||||
check(
|
check(
|
||||||
|
@ -4037,7 +4037,7 @@ pub fn impls_into_future(&self, db: &dyn HirDatabase) -> bool {
|
|||||||
|
|
||||||
let canonical_ty =
|
let canonical_ty =
|
||||||
Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
|
Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
|
||||||
method_resolution::implements_trait(&canonical_ty, db, self.env.clone(), trait_)
|
method_resolution::implements_trait(&canonical_ty, db, &self.env, trait_)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that particular type `ty` implements `std::ops::FnOnce`.
|
/// Checks that particular type `ty` implements `std::ops::FnOnce`.
|
||||||
@ -4052,12 +4052,7 @@ pub fn impls_fnonce(&self, db: &dyn HirDatabase) -> bool {
|
|||||||
|
|
||||||
let canonical_ty =
|
let canonical_ty =
|
||||||
Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
|
Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
|
||||||
method_resolution::implements_trait_unique(
|
method_resolution::implements_trait_unique(&canonical_ty, db, &self.env, fnonce_trait)
|
||||||
&canonical_ty,
|
|
||||||
db,
|
|
||||||
self.env.clone(),
|
|
||||||
fnonce_trait,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Find better API that also handles const generics
|
// FIXME: Find better API that also handles const generics
|
||||||
|
Loading…
Reference in New Issue
Block a user