Add deftype and defeffect to SX type system (Phases 6-7)
Phase 6 — deftype: named type aliases, unions, records, and parameterized types. Type definitions stored as plain dicts in *type-registry*. Includes resolve-type for named type resolution, substitute-type-vars for parameterized instantiation, subtype-resolved? for structural record subtyping, and infer-type extension for record field type inference via get. Phase 7 — defeffect: static effect annotations. Effects stored in *effect-registry* and *effect-annotations*. Supports :effects keyword on defcomp and define. Gradual: unannotated = all effects, empty list = pure. check-body-walk validates effect containment at call sites. Standard types defined: (maybe a), type-def, diagnostic, prim-param-sig. Standard effects declared: io, mutation, render. 84/84 type system tests pass. Both Python and JS bootstrappers succeed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -534,6 +534,11 @@ def strip_prefix(s, prefix):
|
||||
return s[len(prefix):] if s.startswith(prefix) else s
|
||||
|
||||
|
||||
def debug_log(*args):
|
||||
import sys
|
||||
print(*args, file=sys.stderr)
|
||||
|
||||
|
||||
def error(msg):
|
||||
raise EvalError(msg)
|
||||
|
||||
@@ -1255,6 +1260,10 @@ def eval_list(expr, env):
|
||||
return sf_defquery(args, env)
|
||||
elif sx_truthy((name == 'defaction')):
|
||||
return sf_defaction(args, env)
|
||||
elif sx_truthy((name == 'deftype')):
|
||||
return sf_deftype(args, env)
|
||||
elif sx_truthy((name == 'defeffect')):
|
||||
return sf_defeffect(args, env)
|
||||
elif sx_truthy((name == 'begin')):
|
||||
return sf_begin(args, env)
|
||||
elif sx_truthy((name == 'do')):
|
||||
@@ -1504,10 +1513,18 @@ def sf_lambda(args, env):
|
||||
# sf-define
|
||||
def sf_define(args, env):
|
||||
name_sym = first(args)
|
||||
value = trampoline(eval_expr(nth(args, 1), env))
|
||||
has_effects = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))
|
||||
val_idx = (3 if sx_truthy(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))) else 1)
|
||||
value = trampoline(eval_expr(nth(args, val_idx), env))
|
||||
if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))):
|
||||
value.name = symbol_name(name_sym)
|
||||
env[symbol_name(name_sym)] = value
|
||||
if sx_truthy(has_effects):
|
||||
effects_raw = nth(args, 2)
|
||||
effect_list = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effects_raw) if sx_truthy((type_of(effects_raw) == 'list')) else [sx_str(effects_raw)])
|
||||
effect_anns = (env_get(env, '*effect-annotations*') if sx_truthy(env_has(env, '*effect-annotations*')) else {})
|
||||
effect_anns[symbol_name(name_sym)] = effect_list
|
||||
env['*effect-annotations*'] = effect_anns
|
||||
return value
|
||||
|
||||
# sf-defcomp
|
||||
@@ -1522,8 +1539,14 @@ def sf_defcomp(args, env):
|
||||
param_types = nth(parsed, 2)
|
||||
affinity = defcomp_kwarg(args, 'affinity', 'auto')
|
||||
comp = make_component(comp_name, params, has_children, body, env, affinity)
|
||||
effects = defcomp_kwarg(args, 'effects', NIL)
|
||||
if sx_truthy(((not sx_truthy(is_nil(param_types))) if not sx_truthy((not sx_truthy(is_nil(param_types)))) else (not sx_truthy(empty_p(keys(param_types)))))):
|
||||
component_set_param_types(comp, param_types)
|
||||
if sx_truthy((not sx_truthy(is_nil(effects)))):
|
||||
effect_list = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effects) if sx_truthy((type_of(effects) == 'list')) else [sx_str(effects)])
|
||||
effect_anns = (env_get(env, '*effect-annotations*') if sx_truthy(env_has(env, '*effect-annotations*')) else {})
|
||||
effect_anns[symbol_name(name_sym)] = effect_list
|
||||
env['*effect-annotations*'] = effect_anns
|
||||
env[symbol_name(name_sym)] = comp
|
||||
return comp
|
||||
|
||||
@@ -1610,6 +1633,62 @@ def sf_defstyle(args, env):
|
||||
env[symbol_name(name_sym)] = value
|
||||
return value
|
||||
|
||||
# make-type-def
|
||||
def make_type_def(name, params, body):
|
||||
return {'name': name, 'params': params, 'body': body}
|
||||
|
||||
# normalize-type-body
|
||||
def normalize_type_body(body):
|
||||
if sx_truthy(is_nil(body)):
|
||||
return 'nil'
|
||||
elif sx_truthy((type_of(body) == 'symbol')):
|
||||
return symbol_name(body)
|
||||
elif sx_truthy((type_of(body) == 'string')):
|
||||
return body
|
||||
elif sx_truthy((type_of(body) == 'keyword')):
|
||||
return keyword_name(body)
|
||||
elif sx_truthy((type_of(body) == 'dict')):
|
||||
return map_dict(lambda k, v: normalize_type_body(v), body)
|
||||
elif sx_truthy((type_of(body) == 'list')):
|
||||
if sx_truthy(empty_p(body)):
|
||||
return 'any'
|
||||
else:
|
||||
head = first(body)
|
||||
head_name = (symbol_name(head) if sx_truthy((type_of(head) == 'symbol')) else sx_str(head))
|
||||
if sx_truthy((head_name == 'union')):
|
||||
return cons('or', map(normalize_type_body, rest(body)))
|
||||
else:
|
||||
return cons(head_name, map(normalize_type_body, rest(body)))
|
||||
else:
|
||||
return sx_str(body)
|
||||
|
||||
# sf-deftype
|
||||
def sf_deftype(args, env):
|
||||
name_or_form = first(args)
|
||||
body_expr = nth(args, 1)
|
||||
type_name = NIL
|
||||
type_params = []
|
||||
if sx_truthy((type_of(name_or_form) == 'symbol')):
|
||||
type_name = symbol_name(name_or_form)
|
||||
else:
|
||||
if sx_truthy((type_of(name_or_form) == 'list')):
|
||||
type_name = symbol_name(first(name_or_form))
|
||||
type_params = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else sx_str(p)), rest(name_or_form))
|
||||
body = normalize_type_body(body_expr)
|
||||
registry = (env_get(env, '*type-registry*') if sx_truthy(env_has(env, '*type-registry*')) else {})
|
||||
registry[type_name] = make_type_def(type_name, type_params, body)
|
||||
env['*type-registry*'] = registry
|
||||
return NIL
|
||||
|
||||
# sf-defeffect
|
||||
def sf_defeffect(args, env):
|
||||
effect_name = (symbol_name(first(args)) if sx_truthy((type_of(first(args)) == 'symbol')) else sx_str(first(args)))
|
||||
registry = (env_get(env, '*effect-registry*') if sx_truthy(env_has(env, '*effect-registry*')) else [])
|
||||
if sx_truthy((not sx_truthy(contains_p(registry, effect_name)))):
|
||||
registry.append(effect_name)
|
||||
env['*effect-registry*'] = registry
|
||||
return NIL
|
||||
|
||||
# sf-begin
|
||||
def sf_begin(args, env):
|
||||
if sx_truthy(empty_p(args)):
|
||||
@@ -1869,7 +1948,7 @@ BOOLEAN_ATTRS = ['async', 'autofocus', 'autoplay', 'checked', 'controls', 'defau
|
||||
|
||||
# definition-form?
|
||||
def is_definition_form(name):
|
||||
return ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defisland') if sx_truthy((name == 'defisland')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else (name == 'defhandler'))))))
|
||||
return ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defisland') if sx_truthy((name == 'defisland')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))
|
||||
|
||||
# parse-element-args
|
||||
def parse_element_args(args, env):
|
||||
@@ -1995,7 +2074,7 @@ def render_value_to_html(val, env):
|
||||
return escape_html(sx_str(val))
|
||||
|
||||
# RENDER_HTML_FORMS
|
||||
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each']
|
||||
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each']
|
||||
|
||||
# render-html-form?
|
||||
def is_render_html_form(name):
|
||||
@@ -2285,7 +2364,7 @@ def aser_call(name, args, env):
|
||||
return sx_str('(', join(' ', parts), ')')
|
||||
|
||||
# SPECIAL_FORM_NAMES
|
||||
SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland']
|
||||
SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland', 'deftype', 'defeffect']
|
||||
|
||||
# HO_FORM_NAMES
|
||||
HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
|
||||
@@ -2379,7 +2458,7 @@ def aser_special(name, expr, env):
|
||||
elif sx_truthy((name == 'defisland')):
|
||||
trampoline(eval_expr(expr, env))
|
||||
return serialize(expr)
|
||||
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else (name == 'defrelation')))))))))):
|
||||
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'defrelation') if sx_truthy((name == 'defrelation')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect')))))))))))):
|
||||
trampoline(eval_expr(expr, env))
|
||||
return NIL
|
||||
else:
|
||||
@@ -3143,7 +3222,7 @@ async def async_map_render(exprs, env, ctx):
|
||||
return results
|
||||
|
||||
# ASYNC_RENDER_FORMS
|
||||
ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each']
|
||||
ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each']
|
||||
|
||||
# async-render-form?
|
||||
def async_render_form_p(name):
|
||||
@@ -3518,7 +3597,7 @@ async def async_aser_call(name, args, env, ctx):
|
||||
return make_sx_expr(sx_str('(', join(' ', parts), ')'))
|
||||
|
||||
# ASYNC_ASER_FORM_NAMES
|
||||
ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland']
|
||||
ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland', 'deftype', 'defeffect']
|
||||
|
||||
# ASYNC_ASER_HO_NAMES
|
||||
ASYNC_ASER_HO_NAMES = ['map', 'map-indexed', 'filter', 'for-each']
|
||||
@@ -3609,7 +3688,7 @@ async def dispatch_async_aser_form(name, expr, env, ctx):
|
||||
elif sx_truthy((name == 'defisland')):
|
||||
(await async_eval(expr, env, ctx))
|
||||
return serialize(expr)
|
||||
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else (name == 'defaction'))))))))):
|
||||
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))))):
|
||||
(await async_eval(expr, env, ctx))
|
||||
return NIL
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user