corelib

package
v0.8.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

aot_stubs.go — what the interpreter-coupled builtins do in a binary that has no interpreter (ADR 0046 §5).

Four clojure.core names need the analyzer: macroexpand-1, macroexpand, eval, require-go. RegisterAll interns these definitions; pkg/eval OVERWRITES all four with the real ones through the same Def seam when an Evaluator is constructed (internBuiltins), so an interpreted session — REPL, `cljgo run`, the conformance eval half — is completely unchanged. Only an AOT binary, which never constructs an Evaluator, keeps what is here.

The oracle (Clojure 1.12.5, 2026-07-17) cannot decide this one: on the JVM, `(eval '(+ 1 2))` => 3 in AOT-compiled code too, because a JVM program always links clojure.jar — Compiler and all. A cljgo binary follows the CLJS model (design/00, ADR 0001): the compiler is a build-time tool, the binary is plain Go, and there is no analyzer in it to run. ClojureScript answers the same question the same way — `eval` is simply not in cljs.core, and macroexpansion is compile-time only, unless you opt into the self-hosted compiler artifact.

So this is a documented DEVIATION, and it is spelled out rather than papered over:

  • eval / macroexpand / macroexpand-1 are BOUND to a stub that throws "… is not available in an AOT-compiled binary". Bound-and-throwing beats unbound: an unbound var reports "cannot call unbound var: Unbound: #'clojure.core/eval", which reads like a broken boot, while the stub names the real constraint and where it comes from. Referencing the var (resolve, bound?, passing #'eval around) also keeps behaving as in the REPL; only CALLING it fails, and it fails honestly.
  • require-go is a NO-OP, not an error. It is a compile-time directive (it registers Go import aliases for the ANALYZER); by the time a binary replays it, the emitter has already resolved and linked those calls directly. Failing here would break every AOT interop program for no reason (it did — pkg/build's websocket example is the regression test).

`require` is deliberately NOT in this file: ADR 0046 made it work in binaries for real, off the provider registry (require.go).

Package corelib is the Go-native half of clojure.core: every builtin that does not require the tree-walk interpreter (ADR 0043, AOT-core piece 2). RegisterAll interns the whole set into clojure.core without constructing an Evaluator — pkg/eval layers the 5 interpreter-coupled builtins (macroexpand-1, macroexpand, eval, require, require-go) on top via the same Def seam, and piece 3's rt.Boot() will call RegisterAll directly so emitted binaries never link pkg/eval.

Import discipline (test-enforced, imports_test.go): stdlib, pkg/lang, pkg/reader, pkg/version only — never pkg/eval, pkg/analyzer, pkg/ast, pkg/emit.

exceptions.go — the throw/catch normalization shared by BOTH modes (ADR 0007 dual-mode, ADR 0046): the tree-walk OpTry evaluator (pkg/eval) and the AOT emitter's deferred-recover closure (through pkg/emit/rt) call these SAME functions, so behavior is byte-identical by construction — the release-blocker discipline of design/03 §7d. They live in corelib because a compiled binary must shape exceptions without linking the interpreter (piece 3).

host.go — the reflect-backed Go interop substrate (ADR 0010, design/05 §1–§2), shared by BOTH execution paths: the interpreter's evalHost (pkg/eval) and AOT-emitted code (through pkg/emit/rt) call these SAME functions, so `(.Method recv …)` / `(.-Field recv)` / `(pkg/Type. {…})` are byte-identical in REPL and binary by construction. They live in corelib since ADR 0046 (AOT-core piece 3): a compiled binary does interop with no interpreter linked. Only the analysis-time half — alias resolution (require-go), which reads per-evaluator state — stayed in pkg/eval.

printread_builtins.go — the printing + reading extension surface (fundamentals batch A2, 2026-07-23): the Go half of print-method / print-dup / print-simple, Throwable->map, default-data-readers, and the stream-based read / read+string. The print-method and print-dup multimethods themselves are defined in core.clj (defmulti over the -print-class dispatch substrate here); lang.Print consults them through the PrintDispatch seam below, which stays completely inert — one atomic bool load — until the first non-:default method is registered (multimethod_builtins.go's -defmethod flips it), so the native printer's fast path is untouched (perf budgets, design/00 §1.4).

repl_builtins.go — the host seam behind clojure.repl/source-fn (core/repl.cljg, fundamentals audit 2026-07). JVM source-fn reads the var's defining form back out of its source file using :file/:line metadata; cljgo's reader annotates every top-level list with :line/:column/:end-line/:end-column, so the seam reads the file, skips to the line, reads ONE form, and slices the exact source text by the form's position metadata.

require.go — the `require` builtin and the lib-provider registry (ADR 0042 §2, relocated here by ADR 0046).

`require` is NOT interpreter-coupled, and a compiled binary genuinely needs it: emitted code replays every (require …) form at its source position, and that replay is what triggers a dependency package's registered Load(). So the whole libspec surface (:as / :refer / prefix lists) plus the provider registry live here, where a binary can reach them without linking pkg/eval.

The one half that IS interpreter-coupled — making a namespace exist by READING ITS SOURCE FILE — is a hook: pkg/eval installs it (SetLibFileLoader) at evaluator construction, so an interpreted session loads files exactly as before. With no interpreter linked the hook is nil and a require that resolves to neither an existing namespace nor a registered provider fails with a clear error naming the AOT limitation, instead of silently doing nothing.

Index

Constants

This section is empty.

Variables

Out is where println/print/pr/prn write when *out* has no thread binding pointing elsewhere (the root value of *out*, lang.VarOut, is os.Stdout — this package var exists only so tests can swap the default without touching a Var). Package-level and swappable for tests; the REPL driver may point it elsewhere.

Functions

func CallGoMethod

func CallGoMethod(recv any, method string, throw bool, args []any) any

CallGoMethod invokes a Go method by name on a receiver via reflection and shapes the result exactly as a package fn does (design/05 §1–§2, ADR 0010). It is the SINGLE implementation shared by both execution paths: the interpreter calls it directly for OpHostMethod, and AOT-emitted code reaches it through rt.CallMethod — so `(.Method recv arg...)` is byte-identical in REPL and binary by construction (the receiver's static type is unknown in v0, so AOT reflects too). Panics on an unknown method, a coercion failure, or a thrown (`!`) error — recovered at the IFn/recover boundary like every other interop failure.

func CallHostFn

func CallHostFn(name string, rv reflect.Value, argVals []any, throw bool) any

CallHostFn coerces args, reflect-Calls, and shapes the results. It panics (not returns) on a coercion error or a thrown (`!`) error — the interpreter's IFn boundary recovers panics into errors, mirroring builtins.go.

func CatchMatches

func CatchMatches(className string, thrown error) bool

CatchMatches reports whether a catch clause's class symbol matches the thrown value (design/03 §6). The catch-all classes match any thrown value; the standard typed JVM exception names match the cljgo error values that correspond semantically, with the JVM ancestry honored (throwableMatches, ADR 0039 addendum). Unknown class names never match, so the throw propagates — as an unmatched Clojure catch would.

func CheckCyclicLoad

func CheckCyclicLoad(name string)

CheckCyclicLoad panics when name's source file is already mid-load (JVM parity: "Cyclic load dependency"). Namespace existence is no proof of loadedness — a file's (in-ns …) runs before its requires.

func Def

func Def(name string, fn func(args ...any) any) *lang.Var

Def interns a Go builtin into clojure.core: the native IFn wrapper keeps the exact `#object[name]` printing and error→panic boundary the interpreter's def helper always had (design/03 §8).

func DefPrivate

func DefPrivate(name string, fn func(args ...any) any)

DefPrivate interns a core-internal helper (:private true — skipped by refer, invisible to user code by unqualified name).

func GoFieldGet

func GoFieldGet(recv any, field string) any

GoFieldGet reads an exported struct field by name via reflection and normalizes the result exactly as a method/fn result (design/05 §1, ADR 0010). It is the SINGLE implementation shared by both paths: the interpreter calls it for OpHostField, and AOT-emitted code reaches it through rt.FieldGet — so `(.-Field recv)` is byte-identical in REPL and binary. Pointers are auto-dereferenced (Go's field selection does the same). Panics on nil, a non-struct receiver, or an unknown field — recovered at the IFn/recover boundary like every other interop failure.

func GoFieldSet

func GoFieldSet(recv any, field string, val any) any

GoFieldSet assigns an exported struct field by name via reflection and returns the assigned (normalized) value, matching Clojure's set! (design/05 §1, ADR 0010). Shared by both paths (interpreter OpSetBang → OpHostField target; AOT via rt.FieldSet), so byte-identical. Field assignment needs an addressable receiver, so recv MUST be a non-nil pointer to the struct. Panics on a value receiver, an unknown/unexported field, or a coercion failure.

func InitUserNS

func InitUserNS()

InitUserNS creates the `user` namespace, refers clojure.core's publics into it, refers JVM clojure.main's repl-requires set — clojure.repl's doc/source/apropos/dir/pst/find-doc and clojure.pprint's pp/pprint (ADR 0031 established the pattern with doc; the fundamentals-audit 2026-07 batch completes the set) — and roots *ns* at user. It is the tail of the boot sequence (design/00 §6), shared by the interpreter (eval.New) and compiled binaries (rt.Boot, ADR 0046) — one implementation, so the two agree by construction. Callable only after the core sources are loaded.

func InstanceField

func InstanceField(recv any, field string) (any, bool)

InstanceField reads a deftype/defrecord declared field by name — exported for pkg/eval's host dot-form path (GoFieldGet).

func InternClassRefVar

func InternClassRefVar(name string) *lang.Var

InternClassRefVar is classRefVar for an accepted class-name spelling, exported for pkg/emit/rt: a compiled binary hoists every reference to a cljgo.classes var through it so the var is interned BOUND to the same canonical ClassRef the interpreter's resolveVar fallback binds — a plain lang.InternVarName there leaves the var unbound and the binary diverges from the REPL (the ADR 0002/0007 blocker). Returns nil for names outside the ADR 0036 table.

func LookupHostMember

func LookupHostMember(pkg, member string) (reflect.Value, bool)

func LookupHostType

func LookupHostType(pkg, typeName string) (reflect.Type, bool)

func LookupLibProvider

func LookupLibProvider(name string) func()

LookupLibProvider returns the registered loader for a namespace, or nil.

func MakeGoStruct

func MakeGoStruct(pkg, typeName string, fields any) any

MakeGoStruct builds a Go struct from a Clojure field map and returns a POINTER to it (design/05 §1: `(T. {...})` => `&T{...}`), shared by both paths (interpreter OpHostNew; AOT via rt.MakeStruct) so byte-identical. v0 populates via reflection — reflect.New + per-field Set from the keyword map — deferring direct `&T{...}` emission (which needs go/types field typing). fields is a Clojure map (keyword → value) or nil. Panics on an unknown type, a non-keyword key, an unknown/unexported field, or a coercion failure.

func NSResolver

func NSResolver() reader.Resolver

NSResolver returns the reader.Resolver backed by the current namespace (*ns*). The REPL driver, file loads and read-string inject it into their readers.

func NewGoStruct

func NewGoStruct(pkg, typeName string) any

NewGoStruct returns a pointer to a zero-valued Go struct (design/05 §1: `(go/new T)` => `new(T)`), shared by both paths (interpreter OpHostNew Zero; AOT via rt.NewStruct). Panics on an unknown type.

func NewNativeFn

func NewNativeFn(name string, fn func(args ...any) any) lang.IFn

NewNativeFn wraps a Go function as the same native IFn Def interns — the seam pkg/eval's defmacro bootstrap and host-interop path use.

func NormalizeResult

func NormalizeResult(rv reflect.Value) any

NormalizeResult applies nil-normalization then number-normalization to a single Go result (design/05 §2). Nilable kinds (Ptr/Interface/Map/Slice/ Chan/Func) that IsNil() become Clojure nil — so a nil error is falsy in if/when and a non-nil error stays truthy. Go integer/uint kinds fold to int64 and float32/float64 to float64, keeping dual-mode output identical (the printer renders 42, not int(42)).

func PopLibLoading

func PopLibLoading()

PopLibLoading pops the in-progress load stack.

func PushLibLoading

func PushLibLoading(name string)

PushLibLoading marks a lib's file as mid-load (cycle-checked).

func Recover

func Recover(r any) error

Recover normalizes a recovered panic value into the thrown error. cljgo panics carry errors (the IFn boundary and `throw`), but a stray non-error panic is wrapped so catch matching stays total.

func ReferAll

func ReferAll(ns, from *lang.Namespace)

ReferAll refers every public var interned in `from` into ns — the minimal whole-namespace refer of design/00 §6 (M1). Exported for pkg/eval's boot (user ns) and require's :refer :all.

func ReferAsyncAliases

func ReferAsyncAliases(ns *lang.Namespace)

ReferAsyncAliases refers the M4-v0 alias names into ns straight from clojure.core.async — the tail of InitUserNS (user's ReferAll of clojure.core cannot carry them: they are refers there, not interns).

func RegisterAll

func RegisterAll()

RegisterAll pre-interns the interpreter-independent native IFns into clojure.core: the v0 set (+ - * / = < > pr-str println; design/03 §8), the M1 namespace ops (in-ns alias refer), the REPL affordance dynamic vars (*1 *2 *3 *e; design/03 §7b), and the v2 seq/coll primitives that syntax-quote expansions and core.clj's macros consume (list, cons, first, next, rest, second, seq, concat, apply, vector, hash-map, hash-set, with-meta, meta, seq?, string?, not), plus every satellite batch (seq/coll/protocol/multimethod/exception/test/string/ var/transient/numeric/predicate/array/volatile/version/sorted/misc/ format/chan). Namespaces made with `New` refer core's publics, as Clojure's `user` does; a bare in-ns namespace starts empty and reaches core via qualified names or (clojure.core/refer ...). Arithmetic goes through lang's numeric tower (int64 fast path, overflow checked); = is lang.Equiv. Interpreter-coupled builtins (macroexpand-1, macroexpand, eval, require, require-go) are NOT here — pkg/eval registers them per evaluator (ADR 0043).

Idempotent the same way the interpreter's per-New() re-interning always was: BindRoot replaces the root value.

func RegisterLibProvider

func RegisterLibProvider(name string, load func())

RegisterLibProvider registers a loader for a namespace, keyed by its full name ("my-app.util"). Called by generated code via rt.RegisterLib. Last registration wins (re-registration is harmless: providers are guarded, load-once).

func RequireSpec

func RequireSpec(spec any, prefix *lang.Symbol)

RequireSpec processes one require spec: a bare namespace symbol, a libspec vector `[lib & opts]`, or a prefix list `(prefix sub ...)`. `prefix` (may be nil) is the dotted prefix accumulated from an enclosing prefix list.

func ResolveVar

func ResolveVar(sym *lang.Symbol) (*lang.Var, error)

ResolveVar resolves a symbol to a Var against the global namespace world — the body of the analyzer's var-resolution hook (design/03 §3a), a free function since ADR 0043: the "current namespace" is the *ns* dynamic var, not evaluator state, so symbol resolution needs no interpreter (compiled protocol code calls it at load time via -type-key / -instance-of-name?).

func SetLibFileLoader

func SetLibFileLoader(f func(libSym *lang.Symbol))

SetLibFileLoader installs the source-file half of require (pkg/eval's loadLibFile, bound to an evaluator). Namespaces and vars are process-global, so the most recently constructed evaluator wins — the same rule pkg/bri's provider wiring already follows.

func Throw

func Throw(v any) error

Throw normalizes a thrown value into the Go error that `throw` panics. JVM Clojure requires a Throwable; cljgo v0 accepts any value — a value that already satisfies `error` (an ex-info, a Go error surfaced by `!` interop) is thrown as-is; anything else is wrapped in a *ThrownValue so the catch-all classes (Throwable/Exception/RuntimeException) still catch it. This is the faithful-ish v0 relaxation documented on ast.ThrowNode.

func ThrowableCause added in v0.7.0

func ThrowableCause(err error) any

ThrowableCause is java.lang.Throwable.getCause on a host error: the carried cause, else nil. Backs clojure.core/ex-cause and the `.getCause` interop bridge.

func ThrowableMessage added in v0.7.0

func ThrowableMessage(err error) any

ThrowableMessage is java.lang.Throwable.getMessage on a host error: the carried message for a value that has one (*lang.ExceptionInfo), else the rendered error text. It backs BOTH clojure.core/ex-message and the `.getMessage` / `.getLocalizedMessage` interop bridge in CallGoMethod, so the two spellings can never disagree.

Types

type ClassRef

type ClassRef struct {
	// contains filtered or unexported fields
}

ClassRef is the interned value a well-known class-name symbol resolves to. Identity equality (interned singletons); lang.Hash pointer-hashes it.

func (*ClassRef) Name

func (c *ClassRef) Name() string

Name is the canonical fully qualified class name.

func (*ClassRef) String

func (c *ClassRef) String() string

type MultiFn

type MultiFn struct {
	// contains filtered or unexported fields
}

MultiFn is a multimethod value: a dispatch fn, an =-keyed method table, and the dispatch value used as the fallback (:default). It implements lang.IFn (Invoke/ApplyTo) so a var bound to a MultiFn is directly callable — `(area x)` routes through Invoke, exactly like an ordinary fn.

func (*MultiFn) ApplyTo

func (m *MultiFn) ApplyTo(args lang.ISeq) any

func (*MultiFn) Invoke

func (m *MultiFn) Invoke(args ...any) any

Invoke applies the dispatch fn to the args, looks up the matching impl (or :default), and applies it — else a Clojure-shaped "No method ..." error. This is what makes `(a-multifn args...)` work.

func (*MultiFn) String

func (m *MultiFn) String() string

type Protocol

type Protocol struct {
	// contains filtered or unexported fields
}

Protocol is a runtime protocol: its name (for error messages), the method names it declares, and the impl registry keyed typeKey → methodName → fn.

func (*Protocol) String

func (p *Protocol) String() string

type ReifyInstance

type ReifyInstance struct {
	// contains filtered or unexported fields
}

ReifyInstance is an anonymous protocol-satisfying value produced by `(reify P (m [this] ..) ..)` (ADR 0049). Unlike a deftype/defrecord it has NO named type and NO fields — its method bodies are plain `fn` closures that capture the enclosing lexical environment, and its impl table is PER-INSTANCE (keyed by protocol identity → method → fn), not per-type. So it never touches the type-keyed registry: dispatch (-invoke-method) and satisfies? check for a *ReifyInstance receiver first and consult its own table. The method's first param is `this`, bound to the instance itself.

func (*ReifyInstance) String

func (r *ReifyInstance) String() string

type ThrownValue

type ThrownValue struct{ Val any }

ThrownValue wraps a thrown non-error Clojure value (e.g. (throw 42)).

func (*ThrownValue) Error

func (t *ThrownValue) Error() string

type TypeMarker

type TypeMarker struct {
	// contains filtered or unexported fields
}

TypeMarker is the value bound to the type-name var a deftype/defrecord creates (e.g. `Point`): a handle carrying the fully-qualified type name, so `(extend-type Point ...)` and `(instance? Point x)` can recover the dispatch key. Built-in types have no marker — their name symbol resolves straight to a designator string. Since ADR 0039 it also carries the type's REAL ancestry inputs: its kind ("record"/"type") and the protocol values DECLARED in the defining form (extend-type additions deliberately excluded — on the JVM `extend` never alters the class).

func (*TypeMarker) String

func (t *TypeMarker) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL