Skip to content

Commit 099045f

Browse files
roryc89claude
andcommitted
Defer type alias ScopeConflict to reference site
Type aliases that shadow imported types (e.g. `type Output = M.Output`) no longer trigger ScopeConflict at declaration. Instead, the conflict is recorded in `type_scope_conflicts` and only raised when the ambiguous name is actually referenced in a type annotation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a1746fe commit 099045f

3 files changed

Lines changed: 80 additions & 7 deletions

File tree

src/typechecker/check.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,14 +2190,12 @@ fn check_module_impl(module: &Module, registry: &ModuleRegistry, collect_span_ty
21902190
}
21912191
ctx.type_con_arities.insert(qi(name.value), count_kind_arrows(kind));
21922192
}
2193-
Decl::TypeAlias { name, span, .. } => {
2194-
// Type synonyms re-defining an explicitly imported type name are a ScopeConflict.
2195-
// Data/newtype declarations are allowed to shadow imports.
2193+
Decl::TypeAlias { name, .. } => {
2194+
// Local type aliases shadow imported types, just like data/newtype declarations.
2195+
// A ScopeConflict is only raised if the ambiguous name is actually referenced
2196+
// (not merely declared or exported). Record the conflict for deferred checking.
21962197
if explicitly_imported_types.contains(&name.value) {
2197-
errors.push(TypeError::ScopeConflict {
2198-
span: *span,
2199-
name: name.value,
2200-
});
2198+
ctx.type_scope_conflicts.insert(name.value);
22012199
}
22022200
}
22032201
_ => {}
@@ -3252,6 +3250,13 @@ fn check_module_impl(module: &Module, registry: &ModuleRegistry, collect_span_ty
32523250
collect_type_expr_vars(ty, &HashSet::new(), &mut errors);
32533251
// Validate constraint class names in the type signature
32543252
check_constraint_class_names(ty, &known_classes, &class_param_counts, &mut errors);
3253+
// Check for type-level scope conflicts (ambiguous type names)
3254+
if let Some((conflict_span, conflict_name)) = crate::typechecker::convert::find_type_scope_conflict(ty, &ctx.type_scope_conflicts) {
3255+
errors.push(TypeError::ScopeConflict {
3256+
span: conflict_span,
3257+
name: conflict_name,
3258+
});
3259+
}
32553260
match convert_type_expr(ty, &type_ops) {
32563261
Ok(converted) => {
32573262
// Check for partially applied synonyms in type signature

src/typechecker/convert.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,70 @@ pub fn convert_type_expr(ty: &TypeExpr, type_ops: &HashMap<QualifiedIdent, Quali
152152
}
153153
}
154154

155+
/// Check if a type expression references any names that are in scope conflict.
156+
/// Returns the first conflicting name found, if any.
157+
pub fn find_type_scope_conflict(ty: &TypeExpr, conflicts: &HashSet<Symbol>) -> Option<(crate::span::Span, Symbol)> {
158+
match ty {
159+
TypeExpr::Constructor { name, span, .. } => {
160+
if name.module.is_none() && conflicts.contains(&name.name) {
161+
return Some((*span, name.name));
162+
}
163+
None
164+
}
165+
TypeExpr::App { constructor, arg, .. } => {
166+
find_type_scope_conflict(constructor, conflicts)
167+
.or_else(|| find_type_scope_conflict(arg, conflicts))
168+
}
169+
TypeExpr::Function { from, to, .. } => {
170+
find_type_scope_conflict(from, conflicts)
171+
.or_else(|| find_type_scope_conflict(to, conflicts))
172+
}
173+
TypeExpr::Forall { ty, vars, .. } => {
174+
for (_, _, kind) in vars {
175+
if let Some(k) = kind {
176+
if let Some(r) = find_type_scope_conflict(k, conflicts) {
177+
return Some(r);
178+
}
179+
}
180+
}
181+
find_type_scope_conflict(ty, conflicts)
182+
}
183+
TypeExpr::Constrained { constraints, ty, .. } => {
184+
for c in constraints {
185+
for arg in &c.args {
186+
if let Some(r) = find_type_scope_conflict(arg, conflicts) {
187+
return Some(r);
188+
}
189+
}
190+
}
191+
find_type_scope_conflict(ty, conflicts)
192+
}
193+
TypeExpr::Kinded { ty, .. } => {
194+
find_type_scope_conflict(ty, conflicts)
195+
}
196+
TypeExpr::Record { fields, .. } => {
197+
for f in fields {
198+
if let Some(r) = find_type_scope_conflict(&f.ty, conflicts) {
199+
return Some(r);
200+
}
201+
}
202+
None
203+
}
204+
TypeExpr::Row { fields, tail, .. } => {
205+
for f in fields {
206+
if let Some(r) = find_type_scope_conflict(&f.ty, conflicts) {
207+
return Some(r);
208+
}
209+
}
210+
if let Some(t) = tail {
211+
return find_type_scope_conflict(t, conflicts);
212+
}
213+
None
214+
}
215+
_ => None,
216+
}
217+
}
218+
155219
/// Check that kind annotations in forall vars don't forward-reference variables
156220
/// declared later in the same forall. E.g. `forall (a :: k) k.` is invalid because
157221
/// `k` is used before it's declared.

src/typechecker/infer.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ pub struct InferCtx {
111111
/// Names that are ambiguous due to being imported from multiple modules.
112112
/// Referencing these names produces a ScopeConflict error.
113113
pub scope_conflicts: HashSet<Symbol>,
114+
/// Type names that are ambiguous due to a local type alias shadowing an imported type.
115+
/// Only checked when the type name is actually referenced in a type expression.
116+
pub type_scope_conflicts: HashSet<Symbol>,
114117
/// Map from operator → class method target name (e.g. `<>` → `append`).
115118
/// Used for tracking deferred constraints on operator usage.
116119
pub operator_class_targets: HashMap<QualifiedIdent, QualifiedIdent>,
@@ -218,6 +221,7 @@ impl InferCtx {
218221
method_own_constraints: HashMap::new(),
219222
module_mode: false,
220223
scope_conflicts: HashSet::new(),
224+
type_scope_conflicts: HashSet::new(),
221225
operator_class_targets: HashMap::new(),
222226
op_deferred_constraints: Vec::new(),
223227
class_fundeps: HashMap::new(),

0 commit comments

Comments
 (0)