Skip to content
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
30 changes: 15 additions & 15 deletions src/passes/scopes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,10 +618,12 @@ fn walk_stmts<'src>(ast: &Ast<'src>, stmts: &[AstId], scope: ScopeId, ctx: &mut
/// Walk one member/field line of a `type` / `enum` / `union` body.
///
/// `is_enum` selects the constructor-minting semantics: in an `enum`, the
/// leading name of a member is a *binding* (a minted constructor), and only
/// the payload is resolved as type references. In `type`/`union`, names that
/// are field labels are declarations (not resolved); everything else is a
/// type reference.
/// leading name of a member is a *declaration* (a minted constructor) that does
/// NOT leak as a lexical binding -- it is reached via `Enum.Some` / destructure
/// (a type-system concern), so the lexical-scopes pass treats it like a record
/// field name: not bound, not resolved. Only the payload is resolved as type
/// references. In `type`/`union`, field-label names are likewise declarations
/// (not resolved); everything else is a type reference.
fn walk_type_member<'src>(ast: &Ast<'src>, id: AstId, scope: ScopeId, ctx: &mut Ctx<'src>, is_enum: bool) {
let kind = ast.nodes.get(id).kind.clone();
match kind {
Expand All @@ -642,23 +644,21 @@ fn walk_type_member<'src>(ast: &Ast<'src>, id: AstId, scope: ScopeId, ctx: &mut
}
}

// Enum constructor with a payload: `Some T`. The constructor name (func)
// is minted (a binding); the payload args are type references.
NodeKind::Apply { func, args } if is_enum => {
register_pattern_binds(ast, func, scope, ctx);
// Enum constructor with a payload: `Some T`. The constructor name (func) is
// a declaration that does not leak lexically -- skip it; resolve only the
// payload args as type references.
NodeKind::Apply { args, .. } if is_enum => {
for &arg_id in args.items.iter() {
walk_node(ast, arg_id, scope, ctx);
}
}

// A bare name member.
NodeKind::Ident(_) | NodeKind::SynthIdent(_) if is_enum => {
// Nullary enum constructor `None` -- mint it.
register_pattern_binds(ast, id, scope, ctx);
}
// Nullary enum constructor `None`: a declaration, neither bound nor
// resolved at the lexical-scopes level.
NodeKind::Ident(_) | NodeKind::SynthIdent(_) if is_enum => {}

// type tuple positional / union member / enum payload-less non-ident:
// a type reference (or sub-expression to resolve).
// type tuple positional / union member: a type reference (or
// sub-expression to resolve).
_ => walk_node(ast, id, scope, ctx),
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/passes/scopes/test_scope_types.fnk
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ test 'record field names are not references', fn:
bind 0, 'Foo'


# enum members MINT constructors: the constructor name binds, the payload type
# resolves against the generic param.
test 'enum members mint constructors', fn:
# enum member names do NOT leak as lexical bindings (reached via Enum.Some /
# destructure, a type-system concern). The lexical-scopes pass treats the
# constructor name like a record field name: not bound, not resolved. Only the
# generic param binds and the payload type resolves against it.
test 'enum member names are not lexical binds', fn:
expect scope ƒink:
Option = enum T:
Some T
Expand All @@ -43,7 +45,5 @@ test 'enum members mint constructors', fn:

scope 7, 'enum',
bind 1, 'T'
bind 3, 'Some'
ref 'T', 1
bind 6, 'None'
bind 0, 'Option'
Loading