diff --git a/lib/ast.ml b/lib/ast.ml index 45aa3365..d4a94508 100644 --- a/lib/ast.ml +++ b/lib/ast.ml @@ -541,19 +541,6 @@ let fn_body_contains_return : fn_body -> bool = function | FnExpr e -> expr_contains_return e | FnBlock b -> block_contains_return b -(** Free variables of an expression. - - Returns the names used in [expr] but not bound within it; [bound_vars] - lists the names already bound by the enclosing scope (parameters, let - bindings). - - Shared by every pass that needs to know what a body refers to: wasm - codegen, and [Module_loader]'s `use`-list flattening, which uses it to - pull in helpers a selective import never names (`use Dom::{div}` needs - `div`'s own helper `h`; omitting it emitted a module that called an - undefined `h`). Like the other walkers in this module it is deliberately - conservative: constructs it does not inspect contribute [] rather than a - wrong answer. *) (** Variables bound by a pattern. *) let rec pattern_binders (pat : pattern) : string list = match pat with @@ -570,6 +557,20 @@ let rec pattern_binders (pat : pattern) : string list = | PatOr (p1, p2) -> pattern_binders p1 @ pattern_binders p2 | PatAs (id, pat) -> id.name :: pattern_binders pat +(** Free variables of an expression. + + Returns the names used in [expr] but not bound within it; [bound_vars] + lists the names already bound by the enclosing scope (parameters, let + bindings). + + Shared by every pass that needs to know what a body refers to: wasm + codegen, and [Module_loader]'s `use`-list flattening, which uses it to + pull in helpers a selective import never names (`use Dom::{div}` needs + `div`'s own helper `h`; omitting it emitted a module that called an + undefined `h`). Like the other walkers in this module it is deliberately + partial: unhandled constructs, record spreads, shorthand record fields + and qualified constructors contribute no names. The result may contain + duplicates. *) let rec find_free_vars (bound_vars : string list) (expr : expr) : string list = match expr with | ExprLit _ -> [] diff --git a/lib/codegen_deno.ml b/lib/codegen_deno.ml index 426ed16f..cba9648c 100644 --- a/lib/codegen_deno.ml +++ b/lib/codegen_deno.ml @@ -103,6 +103,8 @@ type codegen_ctx = { in_async : bool; } +(** Create an empty emission context for [host], sharing [symbols] and + allocating fresh output and name tables. *) let create_ctx host symbols = { host; output = Buffer.create 1024; @@ -1420,6 +1422,12 @@ let enter_fn_scope ctx (params : param list) : codegen_ctx = call whose head is a known [extern fn] lowers via {!deno_builtins}. ============================================================================ *) +(** Render an expression as JavaScript using the names and host in [ctx]. + Lambdas are synchronous arrows; known qualified constructors refer to + their emitted bindings. Nested statements can update integer tracking. + + @raise Failure for unsupported handlers, resumptions or Bun host calls, + or when a builtin lowering receives too few arguments. *) let rec gen_expr ctx (expr : expr) : string = match expr with | ExprLit lit -> gen_literal lit @@ -1839,6 +1847,10 @@ and gen_try_stmt ctx body catch finally = in "try { " ^ b ^ " } " ^ c ^ f +(** Render a JavaScript statement and update [ctx]'s integer tracking for + subsequent statements. Loop bodies are processed before their tails so + integer division uses the bindings available at each point. + Exceptions from [gen_expr] propagate to the caller. *) and gen_stmt ctx (stmt : stmt) : string = match stmt with | StmtLet { sl_pat = PatWildcard _; sl_value; _ } -> @@ -2134,6 +2146,13 @@ let gen_type_decl ctx (td : type_decl) : unit = a bare struct/alias/extern type carries no runtime value. *) emit_line ctx (Printf.sprintf "// type %s" td.td_name.name) +(** Return a complete JavaScript ES module with the selected host runtime. + Imports must already be flattened. Enum bindings precede other + declarations, and synthesised classes supplement the free functions; + a receiver parameter alone does not make a free function asynchronous. + If a top-level function is named [main], the module awaits its invocation. + Emission exceptions, including [Failure] from [gen_expr], propagate; + [codegen_bun] and [codegen_deno] convert them to [Error] results. *) let generate (host : host_profile) (program : program) (symbols : Symbol.t) : string = let ctx = create_ctx host symbols in (* Register extern names so calls lower via the builtin table, and diff --git a/lib/module_loader.ml b/lib/module_loader.ml index 4b43b91b..d70ed911 100644 --- a/lib/module_loader.ml +++ b/lib/module_loader.ml @@ -107,7 +107,6 @@ let discover_stdlib () = if user_share <> "" && stdlib_dir_valid user_share then user_share else "./stdlib" (* preserves the historical default error path *) -(** Create default configuration *) (** Directories listed in [$AFFINESCRIPT_PATH] (colon-separated, empty entries ignored): where third-party packages such as affinescript-tea live. Searched after the current directory and the stdlib. *) @@ -116,6 +115,10 @@ let env_search_paths () : string list = | None -> [] | Some v -> List.filter (fun d -> d <> "") (String.split_on_char ':' v) +(** Create a configuration using the discovered stdlib, [$AFFINESCRIPT_PATH] + and the current working directory. + + @raise Sys_error if the current working directory cannot be obtained. *) let default_config () : config = { stdlib_path = discover_stdlib (); @@ -276,7 +279,20 @@ let clear_cache (loader : t) : unit = Glob/glob collisions are rejected by the resolver before code generation; this function retains the same last-import policy as a defensive fallback for callers that flatten an already-loaded program directly. Local decls - in [prog.prog_decls] always win over imported ones. *) + in [prog.prog_decls] always win over imported ones. + + Selective imports carry referenced helpers, including private ones, when + [find_free_vars] detects them. That walker is partial: dependencies + referenced only in record spreads, shorthand record fields or qualified + constructors may be omitted. Public enums named directly by type or + constructor in a selective import + are included; enums containing only [Some], [None], [Ok] and [Err] are + excluded from this direct selection because the runtime supplies them. + + [cache] memoises flattened dependencies for this traversal and is updated + in place. [visiting] lists module paths already being traversed; a cycle + uses the cached module's original declarations without further recursion. + Imported declarations are prepended; [prog.prog_imports] is retained. *) let rec flatten_imports_from (cache : (string list, program) Hashtbl.t) (visiting : string list list) (loader : t) @@ -522,6 +538,8 @@ let rec flatten_imports_from { prog with prog_decls = imported_decls @ prog.prog_decls } (** Inline the declarations [prog]'s imports need (transitively) into [prog], - for the backends that compile one flattened program. *) + for the backends that compile one flattened program. Uses only modules + already loaded in [loader]; missing modules are silently skipped. + See [flatten_imports_from] for selection and name precedence. *) let flatten_imports (loader : t) (prog : program) : program = flatten_imports_from (Hashtbl.create 8) [] loader prog diff --git a/lib/resolve.ml b/lib/resolve.ml index 9828ecc9..70ef2f9b 100644 --- a/lib/resolve.ml +++ b/lib/resolve.ml @@ -600,7 +600,9 @@ let lookup_source_scheme (** For an imported type symbol, carry its definition (recorded by the source module under [Typecheck.type_def_key]) so the importer can use the - type's structure — e.g. read an imported struct's fields. *) + type's structure — e.g. read an imported struct's fields. The destination + key uses [bound_name], which may be an alias, and replaces any existing + definition. Non-type symbols and missing definitions leave it unchanged. *) let import_type_def ~dest_name_types ~source_name_types (sym : Symbol.symbol) (bound_name : string) : unit = if sym.Symbol.sym_kind = Symbol.SKType then @@ -613,7 +615,9 @@ let import_type_def ~dest_name_types ~source_name_types [dest_name_types] is the destination type checker's name-keyed scheme map; populating it here is what makes imported functions visible to a freshly - created [Typecheck.check_program] (which keys lookups on name, not sym_id). *) + created [Typecheck.check_program] (which keys lookups on name, not sym_id). + Only [Public] and [PubCrate] symbols are imported, under their original + names; [_alias] is ignored. Available type definitions are copied too. *) let import_resolved_symbols (dest_symbols : Symbol.t) (dest_types : (Symbol.symbol_id, Types.scheme) Hashtbl.t) @@ -638,8 +642,11 @@ let import_resolved_symbols | _ -> () (* Private symbols not imported *) ) source_symbols.all_symbols -(** Import specific items from resolved symbols. See - [import_resolved_symbols] for the role of [dest_name_types]. *) +(** Import specific items from resolved symbols, honouring item aliases for + both value schemes and type definitions. See [import_resolved_symbols] + for the role of [dest_name_types]. Returns [VisibilityError] for an item + outside [Public] or [PubCrate], or [UndefinedVariable] for a missing name, + paired with the item's span. Imports made before an error remain installed. *) let import_specific_items (dest_symbols : Symbol.t) (dest_types : (Symbol.symbol_id, Types.scheme) Hashtbl.t) diff --git a/lib/typecheck.ml b/lib/typecheck.ml index 4c1a80f4..98aaaff1 100644 --- a/lib/typecheck.ml +++ b/lib/typecheck.ml @@ -295,6 +295,9 @@ let unify_eff_or_err (e1 : eff) (e2 : eff) : unit result = (** {1 Context management} *) +(** Create an empty typing context sharing [symbols], with fresh inference + state and registries. Builtins are installed separately by + [register_builtins]. *) let create_context (symbols : Symbol.t) : context = { var_types = Hashtbl.create 128; @@ -448,6 +451,10 @@ let lookup_var (ctx : context) (name : string) : ty result = (** {1 Kind checking} *) +(** Infer a kind using builtin kinds and user arities recorded in [ctx]. + Unregistered type names and unbound variables default to [KType]. + Applications return the remaining kind after consuming their arguments; + argument kind mismatches and over-application return [NotImplemented]. *) let rec infer_kind (ctx : context) (ty : ty) : kind result = match repr ty with | TVar r -> @@ -886,7 +893,13 @@ let record_cell_layout (row : row) : (string * (int * bool)) list option = (** {1 Expression synthesis (mode ⇒)} *) -(** Synthesize a type for an expression. *) +(** Synthesise an expression's type, updating inference state and recording + sites for later elaboration. Zero-parameter lambdas have type [Unit -> T]. + Record updates unify replaced fields with their existing types and add + new fields to closed rows; an open base is constrained to contain the + updated fields and retains its type. Typing and unification errors are + returned; annotation lowering can propagate [Module_resolution_error] + or [Effect_validation_error]. *) let rec synth (ctx : context) (expr : expr) : ty result = match expr with (* Literals *) @@ -1640,7 +1653,11 @@ and check_stmt (ctx : context) (stmt : stmt) : unit result = (** {1 Checking mode (mode ⇐)} *) -(** Check that an expression has the expected type. *) +(** Check that an expression has the expected type, constraining inference + variables in place. A zero-parameter lambda checked against an arrow + requires a [Unit] argument and checks its body against the return type. + Returns typing or unification errors and propagates annotation-lowering + exceptions as in [synth]. *) and check (ctx : context) (expr : expr) (expected : ty) : unit result = match expr with (* Lambda against arrow type: check mode is more precise. @@ -2109,7 +2126,13 @@ let register_builtins (ctx : context) : unit = TApp (TCon "Cmd", [cmd_tv2]), EPure)) -(** Check a top-level function declaration. *) +(** Check a top-level function's signature and body, then bind its generalised + scheme in [ctx]. Externs register their signature without a body check. + On success, parameter and body-local names do not escape into the module. + Kind, body and unification errors are returned; inferred effects outside + an explicit effect row return [EffectNotDeclared]. Annotation lowering + can raise [Module_resolution_error] or [Effect_validation_error]. + An error may leave partially updated context state. *) let check_fn_decl (ctx : context) (fd : fn_decl) : unit result = (* #135 slice 7: register the explicit `` type parameters as fresh, generalizable unification variables before lowering param/return @@ -2245,14 +2268,14 @@ let check_fn_decl (ctx : context) (fd : fn_decl) : unit result = restore_tp (); Ok () -(** Register a type declaration in the context. *) (** Reserved [name_types] key under which a module records the definition of its type [name], so importers can name the type with its structure (an imported struct's fields; imports otherwise carry only value schemes). The NUL byte keeps it disjoint from every identifier. *) let type_def_key (name : string) : string = "\000type:" ^ name -(** Inverse of [type_def_key]: the type name, if [key] is one. *) +(** Extract the non-empty type name from a [type_def_key] key, or [None] + if the prefix is absent or the name is empty. *) let type_of_def_key (key : string) : string option = let pfx = "\000type:" in let n = String.length pfx in @@ -2260,6 +2283,11 @@ let type_of_def_key (key : string) : string option = then Some (String.sub key n (String.length key - n)) else None +(** Register a type definition and any enum constructor schemes in [ctx]. + Parametric enums and extern types record their arities; declarations + without type parameters also export a definition under [type_def_key]. + Returns kind-checking errors. Annotation lowering can propagate + [Module_resolution_error] or [Effect_validation_error]. *) let register_type_decl (ctx : context) (td : type_decl) : unit result = let* ty = match td.td_body with | TyAlias te -> @@ -2533,11 +2561,13 @@ let populate_call_effects (ctx : context) (prog : Ast.program) : unit = ctx.call_effects; Effect_sites.set_async_by_ord async_tbl -(** Learn the arity of every parametric type constructor applied in [ty] +(** Record previously unknown arities of type constructors applied in [ty] (e.g. `Html` in an imported `text : String -> Html`). Imported schemes are the only cross-module type information [check_program] receives, so this is how an imported `enum Html` gets kind - `Type -> Type` in the importer. Builtins keep their fixed kinds. *) + `Type -> Type` in the importer. Builtins keep their fixed kinds, and + existing entries in [ctx.type_arity] are preserved. Row variables are + not followed, including linked row variables. *) let rec record_type_arities (ctx : context) (ty : ty) : unit = let go = record_type_arities ctx in let rec go_row = function @@ -2560,6 +2590,14 @@ let rec record_type_arities (ctx : context) (ty : ty) : unit = | TRef t | TMut t | TOwn t -> go t | TVar _ | TCon _ -> () +(** Check declarations, trait coherence and quantities in a fresh context. + [import_types] supplies value schemes and definitions keyed by + [type_def_key]; local declarations can replace imported bindings. + Generic extern signatures are generalised before bodies are checked. + On success, returns the context and publishes call-effect information + through [Effect_sites]. Typing also records sites for later elaboration. + Returns the first error encountered, converting [Effect_validation_error] + and [Module_resolution_error] to [UnknownEffect] and [UnknownModule]. *) let check_program ?(import_types : (string, scheme) Hashtbl.t option) (symbols : Symbol.t) (prog : Ast.program) : (context, type_error) Result.t =