semantic

package
v0.49.0 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package semantic performs semantic analysis on a parsed Kukicha program: scope construction, symbol resolution, type checking, lint collection, and diagnostic emission. It is the final pre-codegen stage in the compiler pipeline (lexer → parser → semantic → codegen).

Analysis pipeline

Analyze() runs the following phases in order on a single merged *ast.Program. The ordering is load-bearing — later phases assume earlier phases have finished.

  1. Expression maps init. a.exprTypes and a.exprReturnCounts are fresh for this run. Codegen reads them post-Analyze; nothing may read them before Pass 2 finishes writing.
  2. Package name + skill declaration checks. These are cheap standalone validations that don't touch the symbol table.
  3. Directive pre-pass. CollectDirectives walks every declaration's attached directives (# kuki:deprecated, # kuki:security, etc.) and emits TODO lint warnings immediately. The resulting DirectiveResult is consulted by both passes below.
  4. Pass 1 — collectDeclarations. Registers every top-level name (imports, types, interfaces, functions, enums, constants) in the global scope. Does NOT walk function bodies, method bodies, or any expression. Exists solely so Pass 2 can resolve forward references.
  5. Pass 2 — analyzeDeclarations. Walks each declaration and function body. Validates types, type-checks expressions, records ExprTypes / ExprReturnCounts. By this point the global scope is complete; cross-file forward references within a merged program resolve cleanly.
  6. Lint emission. emitLintWarnings flushes any LintCandidate collected during Pass 2 as warnings.

Invariants

  • Pass 1 MUST complete before Pass 2 starts. Moving any logic between them risks forward-reference failures that only surface on large programs.
  • Pass 1 may register symbols but must not analyze bodies, expressions, or do type checking. Adding body-walking to Pass 1 silently breaks mutually recursive type declarations.
  • a.exprTypes / a.exprReturnCounts are populated during Pass 2 and frozen thereafter. Codegen reads them by AST pointer identity — any AST rewrite during or after analysis must copy the entry across or carry the original node through.
  • a.inOnerr / a.currentOnerrAlias are block-scoped conceptually but stored on the Analyzer; save/restore is caller-managed.

Multi-file programs

Today the CLI merges every .kuki file in a target into a single *ast.Program (via cmd/kukicha.mergePrograms) and passes the merged whole here. The two-pass design ensures cross-file references resolve regardless of iteration order.

Phase B of Plan 3 (per-file cache) will split this: each file will be analyzed independently given an Environment of externally- resolved symbols. The Environment type and NewWithEnvironment constructor are the seam where that data will enter the analyzer — see environment.go.

Index

Constants

View Source
const (
	// Names and declarations
	CodeInvalidName              = "semantic/invalid-name"
	CodeRedeclared               = "semantic/redeclared"
	CodeDeclNotTopLevel          = "semantic/decl-not-top-level"
	CodeDuplicateStructField     = "semantic/duplicate-struct-field"
	CodeDuplicateVariantField    = "semantic/duplicate-variant-case-field"
	CodeDuplicateInterfaceMethod = "semantic/duplicate-interface-method"
	CodeDuplicateFunction        = "semantic/duplicate-function"
	CodeDuplicateMethod          = "semantic/duplicate-method"
	CodeDuplicateType            = "semantic/duplicate-type"

	// References
	CodeUndefined           = "semantic/undefined"
	CodeMissingImport       = "semantic/missing-import"
	CodeInvalidImport       = "semantic/invalid-import"
	CodePackageNameConflict = "semantic/package-name-conflict"

	// Types and operands
	CodeTypeMismatch    = "semantic/type-mismatch"
	CodeInvalidOperand  = "semantic/invalid-operand"
	CodeInvalidType     = "semantic/invalid-type"
	CodePlaceholderType = "semantic/placeholder-type"

	// Arguments and calls
	CodeArgumentCount       = "semantic/argument-count"
	CodeArgumentType        = "semantic/argument-type"
	CodeDuplicateNamedArg   = "semantic/duplicate-named-arg"
	CodeUnknownNamedArg     = "semantic/unknown-named-arg"
	CodeNamedArgUnsupported = "semantic/named-arg-unsupported"

	// Return statements
	CodeReturnCount     = "semantic/return-count"
	CodeMisplacedReturn = "semantic/misplaced-return"

	// Assignment
	CodeAssignmentMismatch = "semantic/assignment-mismatch"
	CodeAssignToConst      = "semantic/assign-to-const"

	// Control flow
	CodeMisplacedBreak    = "semantic/misplaced-break"
	CodeMisplacedContinue = "semantic/misplaced-continue"
	CodeInvalidCondition  = "semantic/invalid-condition"

	// Const
	CodeIotaOutsideConst = "semantic/iota-outside-const"
	CodeConstInvalid     = "semantic/const-invalid"

	// For loop
	CodeForRangeType = "semantic/for-range-type"

	// Struct and field access
	CodeUnknownField  = "semantic/unknown-field"
	CodeUnknownMethod = "semantic/unknown-method"

	// Enum
	CodeEnumMixedCases          = "semantic/enum-mixed-cases"
	CodeEnumInvalidCase         = "semantic/enum-invalid-case"
	CodeEnumEmpty               = "semantic/enum-empty"
	CodeEnumUnknownCase         = "semantic/enum-unknown-case"
	CodeEnumParseHelperConflict = "semantic/enum-parse-helper-conflict"
	CodeEnumIterHelperConflict  = "semantic/enum-iter-helper-conflict"

	// Type / package name used where a runtime value is expected
	CodeTypeAsValue = "semantic/type-as-value"

	// Variant enum and is-expression
	CodeVariantUnknownCase    = "semantic/variant-unknown-case"
	CodeVariantTypeMismatch   = "semantic/variant-type-mismatch"
	CodeVariantLiteralInvalid = "semantic/variant-literal-invalid"
	CodeIsBindingInvalid      = "semantic/is-binding-invalid"

	// Lambda
	CodeLambdaMultiReturn = "semantic/lambda-multi-return"

	// Onerr and fallback
	CodeOnerrInvalid        = "semantic/onerr-invalid"
	CodeOnerrUnresolvedCall = "semantic/onerr-unresolved-call"
	CodeFallbackInvalid     = "semantic/fallback-invalid"

	// Pipe
	CodePipeMultiplePlaceholder      = "semantic/pipe-multiple-placeholder"
	CodeShorthandOutsidePipe         = "semantic/shorthand-outside-pipe"
	CodeBlankIdentifierRead          = "semantic/blank-identifier-read"
	CodeReturnInIfExpression         = "semantic/return-in-if-expression"
	CodeReturnInDiscardedPipedSwitch = "semantic/return-in-discarded-piped-switch"

	// Directives
	CodeReturnsDirectiveInvalid = "semantic/returns-directive-invalid"

	// Skill
	CodeSkillInvalid = "semantic/skill-invalid"

	// Lint / warnings
	CodeDeprecated            = "semantic/deprecated"
	CodePotentialPanic        = "semantic/potential-panic"
	CodePipeDiscardsError     = "semantic/pipe-discards-error"
	CodeEnumMissingCase       = "semantic/enum-missing-case"
	CodeEnumRedundantCase     = "semantic/enum-redundant-case"
	CodeEnumZeroMissing       = "semantic/enum-zero-missing"
	CodeOnerrDiscard          = "semantic/onerr-discard"
	CodeOnerrShadow           = "semantic/onerr-shadow"
	CodeOnerrPanic            = "semantic/onerr-panic-in-library"
	CodePipedSwitchMixedTypes = "semantic/piped-switch-mixed-types"
	CodeShadowBuiltin         = "semantic/shadow-builtin"
	CodeValueCtorCapture      = "semantic/value-ctor-capture"

	// Nullable references (#120)
	CodeDerefNullable          = "semantic/deref-nullable"
	CodeUninitializedReference = "semantic/uninitialized-reference"
	CodeNullableInNonNullable  = "semantic/nullable-required"
)

Diagnostic codes emitted by the semantic analyzer. Each code is a slash-separated string "semantic/<rule>", stable across releases. The human-readable message wording may change; the code will not. Codes appear in the Code field of diagnostics.Diagnostic and in --json output.

Variables

View Source
var GeneratedStdlibDocs = map[string]GoStdlibDoc{}/* 1373 elements not displayed */

GeneratedStdlibDocs maps qualified Kukicha stdlib symbol names to their docs. Populated from leading "#" comment blocks and pre-rendered Kukicha signatures so consumers don't need to re-parse stdlib .kuki sources.

Functions

func CollectEnumTypes added in v0.7.1

func CollectEnumTypes(prog *ast.Program) map[string]bool

CollectEnumTypes returns the set of enum type names visible to prog: local enums from its declarations and stdlib enums from its imports (keyed by the local package alias, e.g. "http.Status" when imported as "http"). This is the single authoritative source for both semantic analysis and codegen.

func DeclaredVarTypes added in v0.26.0

func DeclaredVarTypes(fn *ast.FunctionDecl) map[string]string

DeclaredVarTypes maps each of a function's receiver and explicitly-typed parameters to its simple type name (reference wrappers stripped), e.g. `func use(r: Repo)` yields {"r": "Repo"}. Only NamedType/PrimitiveType/ ReferenceType annotations resolve; other forms (list, map, func) are skipped. This is the same conservative, declaration-only resolution the call-graph walk uses to attribute method calls to a concrete receiver type — exported so consumers (e.g. the LSP) can resolve a `recv.Method()` call site to its ReceiverType.Method key without reimplementing the rules.

func DidYouMean added in v0.6.8

func DidYouMean(cand string) string

DidYouMean formats a "did you mean X?" hint, or "" if cand is empty.

func ExportsListHash added in v0.7.0

func ExportsListHash(exportSets []*Exports) string

ExportsListHash returns a deterministic SHA256 of the union of all supplied Exports. The result is the natural per-file cache key component for "what external symbols does this file see?" — two invocations hash equal iff the input set carries the same exported symbols regardless of which file each came from or what order they were passed in.

Today this is conservative: any change in any other file's exports invalidates this file's cache entry, even if this file referenced none of the changed symbols. Phase C narrows that to per-symbol dependency tracking.

func ExternalRefsValidAgainst added in v0.19.7

func ExternalRefsValidAgainst(externalEnvs map[string]*Environment, refs []SymbolRef) bool

ExternalRefsValidAgainst reports whether every cross-package ref still resolves to a symbol with the same hash in externalEnvs. Refs without a Pkg are ignored (those belong to RefsValidAgainst). A missing package, missing symbol, or any hash drift returns false — forcing a re-analysis.

func ForeignIdiomFix added in v0.13.0

func ForeignIdiomFix(name string) *diagnostics.Fix

ForeignIdiomFix returns a machine-applicable fix for a known foreign-idiom identifier whose rewrite is a same-span string substitution, or nil when no trivial replacement exists. The Title is the editor menu-item label; the Replacement is substituted over the diagnostic's own span.

func FuncKey added in v0.26.0

func FuncKey(fn *ast.FunctionDecl) string

FuncKey returns the call-graph / effects-map key for a function or method: the bare name for free functions, "ReceiverType.MethodName" for methods, and "" when the receiver type cannot be resolved to a simple name.

func GetAllSecurityFunctions added in v0.0.30

func GetAllSecurityFunctions() map[string]string

GetAllSecurityFunctions returns the full map of security-checked function names to their category. Callers must not modify the returned map.

func GetAllStdlibEntries added in v0.0.30

func GetAllStdlibEntries() map[string]goStdlibEntry

GetAllStdlibEntries returns the full Kukicha stdlib registry as a read-only map. Callers must not modify the returned map.

func GetAllStdlibEnums added in v0.0.26

func GetAllStdlibEnums() map[string][]string

GetAllStdlibEnums returns the full map of stdlib enum types to their case names.

func GetSecurityCategory

func GetSecurityCategory(qualifiedName string) string

GetSecurityCategory returns the security check category for a stdlib function (e.g., "sql", "html", "fetch", "files", "redirect", "shell"), or "" if none.

func GetSliceGenericClass

func GetSliceGenericClass(qualifiedName string) string

GetSliceGenericClass returns the generic classification for a stdlib/slice function: "T" (uses any), "K" (uses key), "TK" (uses both), or "" (not generic).

func GetStdlibEntry

func GetStdlibEntry(name string) (goStdlibEntry, bool)

GetStdlibEntry returns the Kukicha stdlib registry entry for the given qualified name (e.g., "string.PadRight"). Returns the entry and true if found.

func GetStdlibEnum added in v0.0.26

func GetStdlibEnum(qualifiedName string) ([]string, bool)

GetStdlibEnum returns the case names for a qualified enum type (e.g., "http.Status") and true if found, or nil and false if not an enum.

func GetStdlibStructField added in v0.48.1

func GetStdlibStructField(qualifiedType, fieldName string) (goStdlibType, bool)

GetStdlibStructField returns the declared field type for fieldName on the qualified stdlib plain-struct type qualifiedType (e.g. "chat.Message", "Content"). Returns (zero, false) when the type isn't a registered stdlib struct or has no such exported field. Variant enums are not covered here — use GetStdlibVariant for those.

func GetStdlibVariant added in v0.17.3

func GetStdlibVariant(qualifiedName string) (goStdlibVariant, bool)

GetStdlibVariant returns the variant enum declaration for a qualified name (e.g., "result.Result") if it is a stdlib variant enum, otherwise zero+false.

func GetStdlibVariantCaseParent added in v0.17.3

func GetStdlibVariantCaseParent(qualifiedName string) (string, bool)

GetStdlibVariantCaseParent returns the parent variant enum's qualified name for a qualified case name (e.g., "result.Ok" → "result.Result"). Returns ("", false) when the name is not a stdlib variant case.

func HashSymbol added in v0.7.2

func HashSymbol(sym *Symbol) string

HashSymbol returns a deterministic SHA256 of a single Symbol's (name, kind, exported, type-shape) tuple. Used by Plan 3 Phase C to record per-reference hashes for fine-grained cache invalidation — each cached SymbolRef carries the hash of the symbol the analysis resolved, so a later run can detect a shape change without re-analyzing the whole file.

HashSymbol(sym) is the same hash contributed by sym to (*Exports).Hash; keeping the two definitions consistent means an Exports change that flips a single symbol's hash also flips that symbol's HashSymbol.

func IsKnownInterface

func IsKnownInterface(qualifiedName string) bool

IsKnownInterface returns true if the qualified type name is a known interface from either the Go stdlib or the Kukicha stdlib registries.

func IsValidGoIdentifier added in v0.19.9

func IsValidGoIdentifier(s string) bool

IsValidGoIdentifier reports whether s is a syntactically valid Go identifier (used by codegen to validate auto-aliases).

func LookupParamTypeName added in v0.19.5

func LookupParamTypeName(qualifiedName string, idx int) string

LookupParamTypeName returns the qualified type name of the parameter at position idx for the named stdlib function (Go stdlib first, then Kukicha stdlib). Returns "" if the parameter type is unknown or the function is not registered. Used by codegen to detect when an argument's expected type is a specific interface (e.g., http.Handler) for auto-wrapping.

func RefsValidAgainst added in v0.7.2

func RefsValidAgainst(env *Environment, refs []SymbolRef) bool

RefsValidAgainst reports whether every ref still resolves to a symbol with the same hash in env. A nil-or-missing symbol, or any hash drift, returns false — forcing a re-analysis.

Refs carrying a non-empty Pkg are skipped: they belong to the cross-package validator (ExternalRefsValidAgainst) and have no meaning against a same-package Environment.

func ResolveImportBaseName added in v0.19.9

func ResolveImportBaseName(imp *ast.ImportDecl, sourceFile string) string

ResolveImportBaseName is like ResolveImportName but ignores any alias — it always returns the canonical package name of the imported module.

func ResolveImportName added in v0.19.9

func ResolveImportName(imp *ast.ImportDecl, sourceFile string) string

ResolveImportName returns the package identifier for an import declaration. An explicit alias always wins. Otherwise the canonical `package <name>` declaration is read from the imported module via the Go module graph — necessary because the import path's last segment often diverges from the actual package name (e.g., "github.com/redis/go-redis/v9" → "redis"). Falls back to a path-basename heuristic when module resolution isn't available.

func StdlibDocs added in v0.23.0

func StdlibDocs() map[string]StdlibDocEntry

StdlibDocs returns the full stdlib doc surface keyed by qualified name (e.g., "slice.GroupBy"). Joined from GeneratedStdlibDocs (kind, signature, doc) plus the separately tracked security/deprecated/panics maps.

func Suggest added in v0.6.8

func Suggest(needle string, candidates []string) string

Suggest returns the closest candidate to needle within a small edit distance, or "" if no reasonable match exists. Case-insensitive exact matches are prioritized and returned in the candidate's original casing.

Thresholds are intentionally tight to avoid noisy false-positive hints: needles shorter than 3 runes are not corrected (too many spurious hits), and distance caps at 1 for needles shorter than 6 runes, 2 otherwise.

func SuggestForeignIdiom added in v0.13.0

func SuggestForeignIdiom(name string) string

SuggestForeignIdiom returns a hint message for a known Python or bash identifier, or "" if the name is not a recognized foreign idiom.

Callers should prefer this hint over Levenshtein suggestions when both are available: a user who typed `range` did not typo `Range`, they reached for the wrong tool. Levenshtein remains the fallback for misspellings of real in-scope names.

Types

type AdapterPair added in v0.19.6

type AdapterPair struct {
	Adapter    string
	ImportPath string
	MethodName string
	Params     []AdapterParam
}

AdapterPair carries everything codegen needs to auto-wrap a lambda at a single-method interface parameter position: the adapter func type to wrap with, the package's Go import path so codegen can request the auto-import, the single interface method's name, and the method's parameter types.

func LookupAdapter added in v0.19.6

func LookupAdapter(interfaceQualName string) (AdapterPair, bool)

LookupAdapter returns the adapter pair registered for the single-method interface named interfaceQualName (e.g. "http.Handler" → {"http.HandlerFunc", "ServeHTTP", …}). The second return is false when no pairing is registered.

type AdapterParam added in v0.19.6

type AdapterParam struct {
	Kind TypeKind
	Name string
}

AdapterParam describes one parameter of a single-method interface's method, used by callers (notably codegen) to match a lambda's type annotations against an adapter pair.

type AnalysisResult added in v0.1.0

type AnalysisResult struct {
	Errors   []Diagnostic
	Warnings []Diagnostic

	// CodegenMaps holds the seven AST-keyed maps consumed by codegen. The fields
	// are promoted (accessible as r.ExprTypes, r.ClosedTypeSwitches, etc.) for
	// backward compatibility; new code may also use r.CodegenMaps directly.
	CodegenMaps

	// AllEnumTypes is the authoritative set of enum type names visible to the
	// analyzed program: local enums (plain name) and imported stdlib enums
	// (qualified as "pkg.EnumName", respecting import aliases). Codegen
	// consumes this instead of re-scanning declarations and re-querying
	// GetAllStdlibEnums so enum collection has a single point of truth.
	AllEnumTypes map[string]bool

	// CrossPkgUnitVariantCases is the set of cross-package unit variant case
	// references visible to this file, keyed as "alias.CaseName" (e.g.,
	// "myenums.RED"). Codegen uses this to emit `pkg.Case{}` zero-value
	// literals instead of the bare type name `pkg.Case` that Go rejects as
	// "type, not expression". Only populated when SetExternalEnvironments was
	// called with the file's import dependencies.
	CrossPkgUnitVariantCases map[string]bool

	// CrossPkgValueEnumCases maps a cross-package value-enum (string-/int-backed)
	// case reference, keyed as "alias.CaseName" (e.g. "shared.ACTIVE"), to its
	// enum type name (e.g. "Status"). Codegen uses this to emit the qualified
	// generated constant `pkg.StatusACTIVE` instead of the bare `pkg.ACTIVE`
	// that Go rejects as undefined (#220). Only populated when
	// SetExternalEnvironments was called with the file's import dependencies.
	CrossPkgValueEnumCases map[string]string
}

AnalysisResult bundles all outputs from semantic analysis.

type Analyzer

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

Analyzer performs semantic analysis on the AST

func New

func New(program *ast.Program) *Analyzer

New creates a new semantic analyzer

func NewWithEnvironment added in v0.6.8

func NewWithEnvironment(program *ast.Program, sourceFile string, env *Environment) *Analyzer

NewWithEnvironment creates an Analyzer seeded with externally- resolved symbols from env. The environment's symbols are registered in the global scope before collectDeclarations runs (see seedFromEnvironment), allowing cross-file references to resolve in per-file analysis without seeing the other files' source.

A nil env is equivalent to EmptyEnvironment().

func NewWithFile

func NewWithFile(program *ast.Program, sourceFile string) *Analyzer

NewWithFile creates a new semantic analyzer with the source file path. The file path is used to allow Kukicha stdlib packages to use Go stdlib names.

func (*Analyzer) Analyze

func (a *Analyzer) Analyze() []error

Analyze performs semantic analysis on the program

func (*Analyzer) AnalyzeResult added in v0.1.0

func (a *Analyzer) AnalyzeResult() *AnalysisResult

AnalyzeResult runs Analyze() and returns all outputs in a single struct.

func (*Analyzer) Diagnostics added in v0.0.30

func (a *Analyzer) Diagnostics() []Diagnostic

Diagnostics returns all errors and warnings from the most recent Analyze() call as structured Diagnostic values. The errors come first, then warnings.

Call after Analyze().

func (*Analyzer) ExternalReferences added in v0.19.7

func (a *Analyzer) ExternalReferences() []SymbolRef

ExternalReferences returns the cross-package symbols this analysis resolved through externalEnvs, each paired with a hash of its current shape. Suitable for caching alongside the resulting diagnostics so a later run can detect that a sibling-package symbol's shape changed without re-analyzing the consumer from scratch.

Returns nil when no externalEnvs lookup fired (single-package targets or targets that don't reach any cross-package resolution path). The returned slice is sorted by (Pkg, Name) for determinism.

Call after Analyze().

func (*Analyzer) FieldShapeOf added in v0.8.9

func (a *Analyzer) FieldShapeOf(ti *TypeInfo) FieldShape

FieldShapeOf returns the validation-relevant shape of a field type. reference T transparently uses the underlying T's shape (the rule will apply to the dereferenced value at codegen time).

func (*Analyzer) MarkStdlib added in v0.17.2

func (a *Analyzer) MarkStdlib()

MarkStdlib forces the analyzer into stdlib mode regardless of source path. Use only from tests or call sites that already know they are loading bundled stdlib sources.

func (*Analyzer) References added in v0.7.2

func (a *Analyzer) References() []SymbolRef

References returns the env-seeded symbols this analysis actually resolved, each paired with a hash of its current shape. Suitable for caching alongside the resulting diagnostics. Returns nil when the analyzer was constructed without an environment, or when the env was empty.

Call after Analyze().

func (*Analyzer) SetExternalEnvironments added in v0.19.7

func (a *Analyzer) SetExternalEnvironments(envs map[string]*Environment)

SetExternalEnvironments installs per-import-alias Environments for cross-package qualified-call resolution. The keys must match the alias as it appears in this file's import declarations (the bare last path segment when no alias is given, otherwise the alias identifier). Passing nil clears any previously installed envs.

This is the seam for Phase 3 of #145 — the project loader walks the target's imports, loads each non-stdlib sibling package, extracts its Exports, and hands the resulting Environments here before Analyze() runs. Inside the analyzer, qualified calls that miss the stdlib registries fall through to this map instead of being marked unresolved-external.

func (*Analyzer) SymbolTable added in v0.1.6

func (a *Analyzer) SymbolTable() *SymbolTable

SymbolTable returns the analyzer's symbol table (read-only after Analyze).

type CachedFileResult added in v0.7.2

type CachedFileResult struct {
	Diagnostics []Diagnostic `json:"diagnostics"`
	References  []SymbolRef  `json:"references"`
}

CachedFileResult is the on-disk shape stored under each per-file cache key. Pairs the file's diagnostics with the env symbols it actually consumed, so a later run can check whether any referenced symbol's shape changed before deciding it can reuse the cached diagnostics.

type CachedTargetResult added in v0.19.7

type CachedTargetResult struct {
	Diagnostics  []Diagnostic `json:"diagnostics"`
	ExternalRefs []SymbolRef  `json:"external_refs,omitempty"`
}

CachedTargetResult is the on-disk shape stored under each merged- target cache key. Pairs the target's full diagnostics slice with the cross-package symbols the merged analysis consumed through externalEnvs. A later run re-loads cross-package envs and validates each ref via ExternalRefsValidAgainst before deciding the cached diagnostics are still trustworthy.

Same-package staleness is covered by the cache key (which fingerprints every target source byte); ExternalRefs covers the gap previously papered over by the "skip cache when the target imports a sibling" gate.

type CallEdge added in v0.26.0

type CallEdge struct {
	From string
	To   string
}

CallEdge is a directed caller→callee call relationship; From and To are node keys.

type CallGraph added in v0.26.0

type CallGraph struct {
	Nodes map[string]CallGraphNode
	Edges []CallEdge
}

CallGraph is the project-local call graph: function/method nodes plus the directed caller→callee edges between them. It surfaces the edge set that AnalyzeEffects computes internally and discards. Node keys match the effects map: bare name for free functions, "ReceiverType.Method" for methods. Both endpoints of every edge are present in Nodes.

func AnalyzeCallGraph added in v0.26.0

func AnalyzeCallGraph(programs []*ast.Program) CallGraph

AnalyzeCallGraph builds the project-local call graph from one or more parsed programs. It shares the call-site walk and effect fixpoint with AnalyzeEffects, so its nodes carry the same transitive effect labels. Edges are project-local calls only — calls into stdlib surface as effect labels on the caller node, not as edges. The same resolution limits apply (receiver types only from explicit declarations; no interface or function-value dispatch).

type CallGraphNode added in v0.26.0

type CallGraphNode struct {
	Key      string
	IsMethod bool
	Effects  []string
}

CallGraphNode is one function or method in the graph, carrying its transitive effect set (sorted, possibly empty).

type CodegenMaps added in v0.48.0

type CodegenMaps struct {
	ExprReturnCounts map[ast.Expression]int
	ExprTypes        map[ast.Expression]*TypeInfo
	// ClosedTypeSwitches holds type-switch nodes whose expression resolves to
	// a variant enum. Codegen uses it to emit a `default: panic("unreachable")`
	// backstop without breaking open switches over `any` / interface types.
	ClosedTypeSwitches map[*ast.TypeSwitchStmt]bool
	// ClosedEnumSwitches holds value-switch nodes whose expression resolves to a
	// non-variant enum (string- or int-backed) with every case covered. Codegen
	// emits `default: panic("unreachable")` so Go's return analyzer treats the
	// enclosing function as terminating on all paths.
	ClosedEnumSwitches map[*ast.SwitchStmt]bool
	// EnumValueTypeSwitches holds type-switch nodes whose expression resolves to
	// a non-variant enum (string- or int-backed). Codegen emits a value switch
	// (`switch v := expr; v { case Const: ... }`) for these instead of Go's
	// `.(type)` form.
	EnumValueTypeSwitches map[*ast.TypeSwitchStmt]bool
	// FuncPtrFieldCalls holds method-call nodes whose callee is a
	// `reference func(...)` struct field. Codegen dereferences the pointer at the
	// call site: `obj.field(args)` → `(*obj.field)(args)`.
	FuncPtrFieldCalls map[*ast.MethodCallExpr]bool
	// PipedSwitchAsMultiReturn lists piped-switch expressions used as the sole
	// return value of a multi-return function with uniformly multi-value arms.
	// Codegen reads this to emit `func() (T1, T2, ...) { ... }()` instead of the
	// single-return IIFE form.
	PipedSwitchAsMultiReturn map[*ast.PipedSwitchExpr][]*TypeInfo
}

CodegenMaps bundles the seven AST-keyed maps that semantic analysis produces and codegen consumes. They are grouped here so the handoff between the two passes has a single named unit: AnalysisResult embeds CodegenMaps, and Generator.SetAnalysisResult copies it in one assignment.

All maps are keyed by AST node pointer (frozen after Analyze returns) and are read-only during code generation.

type Diagnostic added in v0.0.30

type Diagnostic = diagnostics.Diagnostic

Diagnostic is a structured compiler diagnostic (error or warning). It is the machine-readable form of the errors and warnings produced by the Analyzer. Use Diagnostics() to obtain them after Analyze().

type DirectiveResult added in v0.1.0

type DirectiveResult struct {
	DeprecatedFuncs map[string]string // Function name → deprecation message
	DeprecatedTypes map[string]string // Type name → deprecation message
	PanickedFuncs   map[string]string // Function name → panic message
}

DirectiveResult holds directive data collected from AST declarations.

func CollectDirectives added in v0.1.0

func CollectDirectives(program *ast.Program) *DirectiveResult

CollectDirectives scans all declarations for # kuki:deprecated and # kuki:panics directives. Returns the collected results.

type Environment added in v0.6.8

type Environment struct {
	// Types holds external type, interface, and enum declarations.
	Types map[string]*Symbol

	// Functions holds external function declarations.
	Functions map[string]*Symbol

	// Constants holds external const declarations.
	Constants map[string]*Symbol

	// Variables holds external top-level `var` declarations. Imports
	// are deliberately excluded — they're file-local and re-derived
	// during each file's analysis (see MergeExports).
	Variables map[string]*Symbol
}

Environment bundles externally-resolved declarations that a file's analysis references but does not own. In today's merged-program flow it is always empty — every symbol the analyzer sees originates from the program it was constructed with. Phase B of Plan 3 (per-file semantic cache) will populate it with the exported symbols of the file's dependency closure so per-file analysis can resolve cross- file references without re-processing every dependency.

Keys are the unqualified name as it appears in source (e.g. "Point") or "pkg.Name" for symbols referenced through an imported package alias.

This type is deliberately inert today: NewWithEnvironment stores it on the analyzer but no pass consults it. Step 3 of Phase B will thread it through collectDeclarations so external symbols seed the global scope before local declarations are scanned.

func EmptyEnvironment added in v0.6.8

func EmptyEnvironment() *Environment

EmptyEnvironment returns an Environment with all maps initialised and empty. Useful as a placeholder where the API wants a non-nil *Environment but the caller has no external symbols to contribute.

func MergeExports added in v0.7.0

func MergeExports(exportSets []*Exports, skip map[string]struct{}) *Environment

MergeExports builds an Environment seeded with the union of all supplied exports, skipping any names listed in skip (typically the caller's own symbols, since a file shouldn't shadow itself with its own previous-pass entries).

On a duplicate name across exports the first occurrence wins. Per- file analysis with the resulting environment will report the conflict via the existing Define() collision path.

Each Symbol is shallow-cloned before being stored in the Environment. This prevents per-file analysis (which mutates sym.Type during const resolution in analyzeConstValue) from writing back through the shared pointer into the originating Exports.Symbols map, which would corrupt cached hash values and stale-entry detection.

func (*Environment) IsEmpty added in v0.6.8

func (env *Environment) IsEmpty() bool

IsEmpty reports whether env carries no external symbols. A nil receiver is also treated as empty.

func (*Environment) Lookup added in v0.7.2

func (env *Environment) Lookup(name string) *Symbol

Lookup finds a symbol by name across env's Types, Functions, and Constants maps. Returns nil if no symbol carries that name. Phase C's cache validator uses this to re-resolve a cached SymbolRef against the current environment so it can compare hashes.

type Exports added in v0.7.0

type Exports struct {
	Symbols map[string]*Symbol
}

Exports captures the top-level declarations a file contributes to its package's scope. In Kukicha's directory-build model every .kuki file in a directory is part of the same package, so all top-level names — exported AND unexported — are visible across files. Exports records every such name with its inferred TypeInfo so other files can resolve cross-file references without re-parsing this file.

Exports is the input to MergeExports → Environment, which seeds an Analyzer constructed via NewWithEnvironment. The Hash() method produces a deterministic identifier suitable for use as the "external dependency" component of a per-file cache key.

func ExtractExports added in v0.7.0

func ExtractExports(prog *ast.Program) *Exports

ExtractExports parses prog and returns its top-level declarations as Symbols. It runs a restricted Pass 1 (collectDeclarations + import registration) on a fresh analyzer so behavior is independent of any caller-supplied environment. Bodies are not analyzed.

Errors emitted during the restricted pass (e.g. invalid identifier names) are intentionally discarded — those errors will be re-emitted by the full per-file analysis path that consumes these exports.

func (*Exports) Hash added in v0.7.0

func (e *Exports) Hash() string

Hash returns a deterministic SHA256 of the exports set, used as the "external dependency" component of per-file cache keys. Two Exports hash equal iff every (name, kind, type-shape) tuple matches.

type FieldShape added in v0.8.9

type FieldShape int

FieldShape categorizes a field type for the rule allowlist.

const (
	ShapeUnknown FieldShape = iota
	ShapeString
	ShapeNumeric       // integer types: int, int64, uint, byte, rune, etc.
	ShapeFloat         // float32, float64 — checked without int() truncation
	ShapeLengthBearing // list, map (length-bearing but not string)
	ShapeStruct        // named struct types — nested validation
)

type FunctionEffects added in v0.19.5

type FunctionEffects map[string][]string

FunctionEffects maps a function or method name to the sorted, deduplicated set of security effect categories it transitively uses. Free functions are keyed by their bare name (e.g., "fetchUsers"). Methods are keyed as "ReceiverType.Method" (e.g., "Repo.Save"). Functions and methods with no observable effects are omitted.

Categories come from the same registry that gates `# kuki:security`-tagged stdlib calls: "sql", "html", "fetch", "files", "shell", "regex", "redirect".

This metadata is read-only — it never gates compilation. Consumers see at a glance which functions touch the database, the network, the filesystem, etc., without trusting a hand-written tag.

func AnalyzeEffects added in v0.19.5

func AnalyzeEffects(programs []*ast.Program) FunctionEffects

AnalyzeEffects computes per-function and per-method effect sets across one or more parsed programs. Phases: build a project-local call graph keyed by free-function name and by "ReceiverType.Method"; seed each entry's set from qualified stdlib calls; propagate to a fixpoint.

Limitations:

  • Receiver types are resolved only from explicit parameter and receiver declarations (e.g. `func use(r: Repo)` yields r→Repo); local variables assigned from calls or composite literals are not tracked.
  • Indirect calls through function-typed values are not tracked.
  • Only stdlib `# kuki:security` categories are seeded; user-tagged effects are not supported by design (effect inference, not annotation).

type GoStdlibDoc added in v0.23.0

type GoStdlibDoc struct {
	Kind      string
	Signature string
	Doc       string
}

GoStdlibDoc is the agent-facing surface for a stdlib symbol: its kind ("func", "type", "enum", "interface"), a pre-rendered Kukicha signature, and the doc-comment block that preceded its declaration. Exported so the "kukicha context --stdlib" CLI can read it without depending on internals the rest of the package keeps private. Security and deprecation strings for the same symbol live in the generatedSecurityFunctions and generatedStdlibDeprecated maps and are joined at the consumer.

type LintCandidate added in v0.1.0

type LintCandidate struct {
	Kind     LintKind
	Pos      ast.Position
	Message  string
	Category string // explicit category; empty means infer from message text
}

LintCandidate captures a potential warning during analysis, to be emitted in a separate pass after type checking completes.

type LintKind added in v0.1.0

type LintKind int

LintKind categorizes lint warnings for filtering and configuration.

const (
	LintDeprecation LintKind = iota
	LintPanic
	LintOnerr
	LintPipe
	LintEnum
	LintTypeMismatch
	LintSecurity
	LintShadowBuiltin
	LintNilDeref
	LintUnusedLoopVar
	LintNewBuiltin
	LintTypedNilEmpty
	LintNamedArgs
	LintValueCtorCapture
)

type Scope

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

Scope represents a lexical scope

func NewScope

func NewScope(parent *Scope) *Scope

NewScope creates a new scope

func (*Scope) Define

func (s *Scope) Define(symbol *Symbol) error

Define adds a symbol to the current scope

func (*Scope) Resolve

func (s *Scope) Resolve(name string) *Symbol

Resolve looks up a symbol in the current scope and parent scopes

func (*Scope) ResolveLocal added in v0.48.1

func (s *Scope) ResolveLocal(name string) *Symbol

ResolveLocal looks up a symbol in this scope only, without walking parents. Used to implement Go's short-var-decl (`:=`) redeclaration rule, which reassigns an existing name only when it lives in the *current* block scope (a name from an outer scope is shadowed by a fresh declaration instead).

func (*Scope) TypeNames added in v0.6.8

func (s *Scope) TypeNames() []string

TypeNames walks the scope chain and returns the names of type and interface symbols (no variables, functions, or constants).

func (*Scope) VisibleNames added in v0.6.8

func (s *Scope) VisibleNames() []string

VisibleNames walks the scope chain and returns all defined names.

type SecurityChecker added in v0.1.0

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

SecurityChecker performs security analysis on method calls. It detects SQL injection, XSS, SSRF, command injection, path traversal, and open redirect vulnerabilities.

type StdlibDocEntry added in v0.23.0

type StdlibDocEntry struct {
	Kind       string `json:"kind"`
	Signature  string `json:"signature"`
	Doc        string `json:"doc,omitempty"`
	Security   string `json:"security,omitempty"`
	Deprecated string `json:"deprecated,omitempty"`
	Panics     string `json:"panics,omitempty"`
}

StdlibDocEntry is the agent-facing record for one stdlib symbol, joining the generator's doc map with the per-symbol security and deprecation strings tracked separately. Returned by StdlibDocs for the `kukicha context --stdlib` CLI.

type Symbol

type Symbol struct {
	Name     string
	Kind     SymbolKind
	Type     *TypeInfo
	Defined  ast.Position
	Mutable  bool
	Exported bool
	IsAlias  bool // true for transparent `type X = Y`; false for defined `type X Y`
	// OriginEnum is set on symbols auto-generated by an enum declaration
	// (currently the `Parse<EnumName>` helper from string-backed enums).
	// Empty for everything else. Used to produce a targeted diagnostic when
	// the user also writes a function with the generated name.
	OriginEnum string
	// IsImport marks symbols that were registered as the local binding for
	// an `import` declaration (currently encoded as Kind=SymbolVariable
	// because there is no dedicated SymbolImport). Cross-file/package
	// export merging uses this to skip imports — they're file-local — while
	// still propagating genuine top-level `var` declarations.
	IsImport bool
}

Symbol represents a symbol in the symbol table

type SymbolKind

type SymbolKind int

SymbolKind represents the kind of symbol

const (
	SymbolVariable SymbolKind = iota
	SymbolParameter
	SymbolFunction
	SymbolType
	SymbolInterface
	SymbolConst
)

func (SymbolKind) String

func (sk SymbolKind) String() string

type SymbolRef added in v0.7.2

type SymbolRef struct {
	Pkg  string `json:"pkg,omitempty"`
	Name string `json:"name"`
	Hash string `json:"hash"`
}

SymbolRef is a name + symbol-hash pair recording that an analysis consumed an external symbol with a particular shape. Per-file cache entries store the slice of refs they produced; on lookup the validator re-resolves each name against the current environment and compares hashes — a mismatch (or missing symbol) means the cache entry is stale and must be regenerated.

Pkg, when non-empty, marks the ref as cross-package: the symbol came from externalEnvs[Pkg] (the consumer's import alias) rather than the same-package Environment. Same-package refs leave Pkg empty for backward compatibility with existing per-file cache entries.

type SymbolTable

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

SymbolTable manages scopes and symbols

func NewSymbolTable

func NewSymbolTable() *SymbolTable

NewSymbolTable creates a new symbol table

func (*SymbolTable) CurrentScope

func (st *SymbolTable) CurrentScope() *Scope

CurrentScope returns the current scope

func (*SymbolTable) Define

func (st *SymbolTable) Define(symbol *Symbol) error

Define adds a symbol to the current scope

func (*SymbolTable) EnterScope

func (st *SymbolTable) EnterScope()

EnterScope creates a new scope

func (*SymbolTable) ExitScope

func (st *SymbolTable) ExitScope()

ExitScope removes the current scope

func (*SymbolTable) Resolve

func (st *SymbolTable) Resolve(name string) *Symbol

Resolve looks up a symbol. Successful resolutions trigger onResolve (if set) so callers can observe which symbols this analysis actually consumed — used by the Phase C per-file cache to track outbound references against env-seeded symbols.

func (*SymbolTable) ResolveLocal added in v0.48.1

func (st *SymbolTable) ResolveLocal(name string) *Symbol

ResolveLocal looks up a name in the current scope only (no parent walk and no onResolve callback). Used by short-var-decl (`:=`) analysis to decide whether a name is a redeclaration in the same block.

func (*SymbolTable) TypeNames added in v0.6.8

func (st *SymbolTable) TypeNames() []string

TypeNames returns the names of every type/interface symbol reachable from the current scope. Used for "did you mean …?" hints on type references.

func (*SymbolTable) VisibleNames added in v0.6.8

func (st *SymbolTable) VisibleNames() []string

VisibleNames returns the names of every symbol reachable from the current scope walking parent scopes. Used for "did you mean …?" hints.

type TypeInfo

type TypeInfo struct {
	Kind          TypeKind
	Name          string               // For named types and placeholders
	ElementType   *TypeInfo            // For lists, channels, references
	KeyType       *TypeInfo            // For maps
	ValueType     *TypeInfo            // For maps
	Params        []*TypeInfo          // For functions
	Returns       []*TypeInfo          // For functions
	Constraint    string               // For placeholders: "any", "comparable", "cmp.Ordered"
	Variadic      bool                 // For functions: true if last param is variadic
	ParamNames    []string             // For functions: parameter names (for named argument validation)
	DefaultCount  int                  // For functions: number of parameters with default values
	Fields        map[string]*TypeInfo // For structs: field name → field type
	Methods       map[string]*TypeInfo // For structs: method name → function TypeInfo
	EnumCases     map[string]*TypeInfo // For enums: case name → case type (the enum type itself)
	VariantCases  map[string]*TypeInfo // For variant enums: case name → struct TypeInfo with fields
	VariantParent *TypeInfo            // For variant case structs: pointer to the parent variant enum type
	TypeParams    []string             // For generic variant enums and their cases: declared type parameter names (e.g., ["T"]). Empty for non-generic types.
	TypeArgs      []*TypeInfo          // For instantiated generic types (e.g. `ApplyResult of GKEOutputs`): concrete type substituted into TypeParams. Populated by Unit 5.
	Nullable      bool                 // For references: true for `nullable reference T` (may hold empty). Codegen-irrelevant; both emit `*T`.
	AliasOf       string               // For transparent named-type aliases: the underlying type name (e.g. "Status" for `type MyStatus = Status`). Empty otherwise.
}

TypeInfo represents type information

func (*TypeInfo) String

func (ti *TypeInfo) String() string

type TypeKind

type TypeKind int

TypeKind represents the kind of type

const (
	TypeKindUnknown TypeKind = iota
	TypeKindInt
	TypeKindFloat
	TypeKindString
	TypeKindBool
	TypeKindList
	TypeKindMap
	TypeKindChannel
	TypeKindReference
	TypeKindFunction
	TypeKindStruct
	TypeKindInterface
	TypeKindNamed
	TypeKindEnum
	TypeKindVariant     // For variant enums (data-carrying sum types)
	TypeKindPlaceholder // For generic type placeholders (element, item, etc.)
	TypeKindNil         // For the 'empty' keyword (nil)
	TypeKindTypeParam   // For user-declared generic type parameters on variant enums (`enum X of T`).
)

func (TypeKind) GoConstName added in v0.25.2

func (tk TypeKind) GoConstName() string

GoConstName returns the Go source identifier for this TypeKind constant (e.g. TypeKindInt → "TypeKindInt"). The stdlib generators (cmd/genstdlibregistry, cmd/gengostdlib) use it to emit goStdlibType literals whose Kind field is anchored to these constants: because every arm references a real TypeKind, a renamed or removed constant breaks the generators at compile time instead of silently emitting a stale string.

func (TypeKind) String

func (tk TypeKind) String() string

type ValidateRule added in v0.8.9

type ValidateRule struct {
	Name string
	Arg  string
}

ValidateRule is a parsed `# kuki:validate` rule.

Examples (from the directive arg `"nonempty,min=1,oneof=a|b|c"`):

{Name: "nonempty"}
{Name: "min", Arg: "1"}
{Name: "oneof", Arg: "a|b|c"}

func ParseValidateRules added in v0.8.9

func ParseValidateRules(spec string) []ValidateRule

ParseValidateRules splits a directive arg like `"nonempty,min=1"` into individual ValidateRule entries. Whitespace around names and args is trimmed; pipe (|) inside an argument is kept intact so `oneof=a|b|c` works while keeping comma as the rule separator.

Jump to

Keyboard shortcuts

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