Skip to content

Commit b61a6d0

Browse files
committed
wip: more salsa
1 parent 9d47e4f commit b61a6d0

20 files changed

Lines changed: 796 additions & 508 deletions

compiler/hir/hir_def/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ mod dependency_graph;
55
mod diagnostics;
66
mod hir;
77
mod hir_module;
8+
mod value_def;
89

910
pub use diagnostics::*;
1011
pub use hir::*;
1112
pub use hir_module::HirModule;
13+
pub use value_def::{module_value_def, ValueDef};
1214

1315
mod fqn;
1416
pub use fqn::{Fqn, FqnResolutionError};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use crate::{hir::lower_file, ExpressionIdx, HirDefDatabase, Name, TypeIdx};
2+
use alloy_workspace::ModuleId;
3+
4+
/// A Salsa-tracked value definition.
5+
///
6+
/// Each field is independently tracked by Salsa, so downstream queries
7+
/// that only read `type_annotation` won't re-run when `expression_idx` changes.
8+
#[salsa::tracked]
9+
pub struct ValueDef<'db> {
10+
pub module_id: ModuleId,
11+
#[returns(ref)]
12+
pub name: Name,
13+
pub type_annotation: Option<TypeIdx>,
14+
pub expression_idx: ExpressionIdx,
15+
}
16+
17+
/// Returns one `ValueDef` per top-level value definition in the module.
18+
///
19+
/// These are created from the same data as `lower_file`, but as Salsa-tracked
20+
/// structs so that downstream queries can depend on individual fields.
21+
#[salsa::tracked]
22+
pub fn module_value_def<'db>(
23+
db: &'db dyn HirDefDatabase,
24+
module_id: ModuleId,
25+
expr_idx: ExpressionIdx,
26+
) -> Option<ValueDef<'db>> {
27+
let (hir_module, _) = lower_file(db, module_id);
28+
29+
hir_module
30+
.get_value_by_id(&expr_idx)
31+
.map(|v| ValueDef::new(db, module_id, v.name.clone(), v.type_annotation, v.value))
32+
}

compiler/hir/hir_infer/src/hir_ty/hm/constraint_gen/expr/mod.rs

Lines changed: 106 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use super::{HMInferenceContext, MonoType};
2-
use crate::hir_ty::hm::inference::annotated_to_mono;
32
use crate::hir_ty::hm::PolyType;
3+
use crate::hir_ty::InferredType;
4+
use crate::value_signature::infer_value_signature;
45
use alloy_hir_def as hir;
56
use alloy_hir_resolved as res;
6-
use alloy_hir_resolved::{resolve_annotated_type, Fql};
7+
use alloy_hir_resolved::Fql;
8+
use rustc_hash::FxHashMap;
79

810
mod binary;
911
mod function_call;
@@ -23,35 +25,35 @@ pub(crate) fn infer_expr_hm(
2325
return existing_ty;
2426
}
2527

26-
// Lazy constraint generation: if this expression is in a later group,
27-
// don't infer it now - just return a fresh type variable.
28-
// Only applies to expressions in the current module — cross-module expressions
29-
// have their own independent ordering and must always be inferred immediately.
30-
// DON'T assign to type_env to avoid polluting env_type_vars for generalization
31-
if source_fql.module_id == ctx.module_id && ctx.is_in_later_group(source_fql.local_id) {
32-
return ctx.fresh_type_var();
33-
}
28+
// Use `infer_value_signature` for cross-module value definitions and
29+
// same-module definitions with polymorphic annotations (when referenced
30+
// from other definitions, not when being directly inferred).
31+
//
32+
// Skip the shortcut for:
33+
// - The expression currently being inferred (inferring_expr) to prevent cycles
34+
// and ensure sub-expression types are collected
35+
// - Same-module unannotated definitions, so call-site constraints flow back
36+
let is_self = ctx
37+
.inferring_expr
38+
.as_ref()
39+
.is_some_and(|e| *e == source_fql);
40+
if !is_self {
41+
if let Some(value_def) =
42+
hir::module_value_def(ctx.db, source_fql.module_id, source_fql.local_id)
43+
{
44+
let is_cross_module = source_fql.module_id != ctx.module_id;
45+
let has_poly_annotation = value_def
46+
.type_annotation(ctx.db)
47+
.map(|ta| {
48+
alloy_hir_resolved::resolve_annotated_type(ctx.db, source_fql.module_id, ta)
49+
.is_polymorphic()
50+
})
51+
.unwrap_or(false);
3452

35-
// For cross-module expressions with polymorphic type annotations, use the annotation
36-
// directly rather than re-inferring from the body. This ensures each use site gets
37-
// fresh type variables via poly_env instantiation, avoiding stale specialization
38-
// when a polymorphic cross-module function (e.g., <|) is used multiple times.
39-
if source_fql.module_id != ctx.module_id {
40-
let (other_hir_module, _) = hir::lower_file(ctx.db, source_fql.module_id);
41-
if let Some(value) = other_hir_module.get_value_by_id(&source_fql.local_id) {
42-
if let Some(type_annotation) = value.type_annotation {
43-
let annotated =
44-
resolve_annotated_type(ctx.db, source_fql.module_id, type_annotation);
45-
if annotated.is_polymorphic() {
46-
if let Some(mono_ty) = annotated_to_mono(&annotated, ctx) {
47-
let poly_ty = PolyType::generalize_all(mono_ty);
48-
ctx.poly_env.insert(source_fql.clone().into(), poly_ty);
49-
// Return a fresh instantiation
50-
if let Some(instantiated) = ctx.maybe_find_type(&source_fql) {
51-
return instantiated;
52-
}
53-
}
54-
}
53+
if is_cross_module || has_poly_annotation {
54+
let sig = infer_value_signature(ctx.db, value_def);
55+
let mono_ty = inferred_to_mono(&sig, ctx);
56+
return ctx.generalize_to_poly(mono_ty, &source_fql);
5557
}
5658
}
5759
}
@@ -108,6 +110,80 @@ pub(crate) fn infer_expr_hm(
108110
}
109111
}
110112

113+
/// Convert an InferredType (from `infer_value_signature`) to a MonoType for use
114+
/// in constraint generation. Generic IDs are mapped to fresh type variables,
115+
/// with consistent mapping so the same Generic(id) produces the same TypeVarId.
116+
fn inferred_to_mono(inferred: &InferredType, ctx: &mut HMInferenceContext) -> MonoType {
117+
let mut generic_map: FxHashMap<usize, super::super::TypeVarId> = FxHashMap::default();
118+
inferred_to_mono_inner(inferred, ctx, &mut generic_map)
119+
}
120+
121+
fn inferred_to_mono_inner(
122+
inferred: &InferredType,
123+
ctx: &mut HMInferenceContext,
124+
generic_map: &mut FxHashMap<usize, super::super::TypeVarId>,
125+
) -> MonoType {
126+
match inferred {
127+
InferredType::Unconstrained => MonoType::Unconstrained,
128+
InferredType::Missing => ctx.fresh_type_var(),
129+
InferredType::Unit => MonoType::Unit,
130+
InferredType::BuiltIn(b) => MonoType::Concrete(*b),
131+
InferredType::TypeDef(fql, name) => MonoType::TypeDef {
132+
fql: fql.clone(),
133+
type_args: vec![],
134+
type_def_name: name.clone(),
135+
},
136+
InferredType::Lambda {
137+
arg_type,
138+
return_type,
139+
} => MonoType::Function(
140+
Box::new(inferred_to_mono_inner(arg_type, ctx, generic_map)),
141+
Box::new(inferred_to_mono_inner(return_type, ctx, generic_map)),
142+
),
143+
InferredType::Tuple(elements) => MonoType::Tuple(
144+
elements
145+
.iter()
146+
.map(|e| inferred_to_mono_inner(e, ctx, generic_map))
147+
.collect(),
148+
),
149+
InferredType::Bounded { base, args } => {
150+
// When the base is a TypeDef, populate its type_args with fresh TypeVarIds
151+
// to preserve arity information (used in error messages and display)
152+
let constructor = match base.as_ref() {
153+
InferredType::TypeDef(fql, name) => {
154+
let type_args = args.iter().map(|_| ctx.type_var_gen.fresh()).collect();
155+
MonoType::TypeDef {
156+
fql: fql.clone(),
157+
type_args,
158+
type_def_name: name.clone(),
159+
}
160+
}
161+
other => inferred_to_mono_inner(other, ctx, generic_map),
162+
};
163+
MonoType::App {
164+
constructor: Box::new(constructor),
165+
args: args
166+
.iter()
167+
.map(|a| inferred_to_mono_inner(a, ctx, generic_map))
168+
.collect(),
169+
}
170+
}
171+
InferredType::Generic(id) => {
172+
let var_id = *generic_map
173+
.entry(*id)
174+
.or_insert_with(|| ctx.type_var_gen.fresh());
175+
MonoType::Var(var_id)
176+
}
177+
InferredType::ConstrainedGeneric { id, .. } => {
178+
// TODO: Track constraints during solving
179+
let var_id = *generic_map
180+
.entry(*id)
181+
.or_insert_with(|| ctx.type_var_gen.fresh());
182+
MonoType::Var(var_id)
183+
}
184+
}
185+
}
186+
111187
fn infer_missing_expr(ctx: &mut HMInferenceContext, source_fql: Fql<hir::Expression>) -> MonoType {
112188
let ty = ctx.fresh_type_var();
113189
ctx.assign_type(source_fql, ty)

compiler/hir/hir_infer/src/hir_ty/hm/constraint_gen/expr/type_def.rs

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,8 @@ pub(super) fn infer_variant_constructor(
7070
// Build the constructor type using the shared helper
7171
let constructor_ty = build_constructor_type(ctx, &type_def_fql, &type_def, &member);
7272

73-
// Check if this is a polymorphic constructor
74-
let is_polymorphic = has_type_variables(&constructor_ty);
75-
76-
if is_polymorphic {
77-
// Generalize and store in poly_env for proper instantiation
78-
// For variant constructors, quantify over ALL free variables (not filtered by env_type_vars)
79-
// since constructors are top-level polymorphic values
80-
let poly_ty = PolyType::generalize_all(constructor_ty.clone());
81-
ctx.poly_env.insert(variant_fql.clone(), poly_ty);
82-
83-
if let Some(tracked_ty) = ctx.maybe_find_type(variant_fql) {
84-
return ctx.assign_type(source_fql, tracked_ty);
85-
}
86-
}
87-
88-
ctx.assign_type(source_fql, constructor_ty)
73+
let tracked_ty = ctx.generalize_to_poly(constructor_ty, variant_fql);
74+
ctx.assign_type(source_fql, tracked_ty)
8975
}
9076

9177
pub(super) fn infer_type_definition(
@@ -115,18 +101,7 @@ pub(super) fn infer_type_definition(
115101
// When you call Identity(...), it's the same as Id(...)
116102
let constructor_ty = build_constructor_type(ctx, &td_fql, &type_def, member);
117103

118-
// Check if this is a polymorphic constructor (has App with type variables)
119-
let is_polymorphic = has_type_variables(&constructor_ty);
120-
121-
if is_polymorphic {
122-
// Generalize and store in poly_env for proper instantiation
123-
// For type definition constructors, quantify over ALL free variables
124-
// since constructors are top-level polymorphic values
125-
let poly_ty = PolyType::generalize_all(constructor_ty.clone());
126-
ctx.poly_env.insert(td_fql.clone().into(), poly_ty);
127-
}
128-
129-
constructor_ty
104+
ctx.generalize_to_poly(constructor_ty, &td_fql)
130105
}
131106
TypeDefinitionKind::Union(_members) => {
132107
// Multi-variant type - cannot be called as a function directly
@@ -209,19 +184,3 @@ fn build_constructor_type(
209184
MonoType::Function(Box::new(param_ty), Box::new(acc))
210185
})
211186
}
212-
213-
/// Check if a MonoType contains type variables (is polymorphic)
214-
fn has_type_variables(ty: &MonoType) -> bool {
215-
match ty {
216-
MonoType::Var(_) => true,
217-
MonoType::Function(arg, ret) => has_type_variables(arg) || has_type_variables(ret),
218-
MonoType::Tuple(elements) => elements.iter().any(has_type_variables),
219-
MonoType::App { constructor, args } => {
220-
has_type_variables(constructor) || args.iter().any(has_type_variables)
221-
}
222-
MonoType::Unconstrained
223-
| MonoType::Concrete(_)
224-
| MonoType::TypeDef { .. }
225-
| MonoType::Unit => false,
226-
}
227-
}

0 commit comments

Comments
 (0)