package sema
import (
"github.com/marzeq/qk/parser"
"github.com/marzeq/qk/shared"
"github.com/marzeq/qk/symbols"
"github.com/marzeq/qk/types"
)
type Analyser struct {
universe *symbols.Scope
current *symbols.Scope
modules map[string]*symbols.Module
aliases map[string]*aliasInfo
aliasesByModule map[string]map[string]*aliasInfo
methods map[string]map[string]*symbols.Symbol
structuralMethods map[string][]*symbols.Symbol
concreteTypes map[string]types.Type
nominalTraits map[string]types.TraitType
errors []error
currentMod string
currentTrustedStandardLibrary bool
currentImports map[string]bool
importsByModule map[string]map[string]bool
currentImportAliases map[string]string
importAliasesByModule map[string]map[string]string
typeParameterBindings map[string]types.Type
resolvingTraitMethodTypes bool
currentRoot *parser.RootNode
modulePaths map[*parser.RootNode]string
functionDefinitions map[*symbols.Symbol]*functionDefinitionInfo
genericFunctions map[*symbols.Symbol]*genericFunctionInfo
genericValues map[*symbols.Symbol]*genericValueInfo
genericAliases map[*symbols.Symbol]*genericAliasInfo
traitDefaults map[string][]*symbols.Symbol
definitionTypeBindings map[*symbols.Symbol]map[string]types.Type
lambdaCounter int
lambdaOwnedSymbols []map[*symbols.Symbol]bool
}
func NewAnalyser() *Analyser {
u := symbols.NewScope(nil)
a := &Analyser{
universe: u,
modules: make(map[string]*symbols.Module),
aliases: make(map[string]*aliasInfo),
aliasesByModule: make(map[string]map[string]*aliasInfo),
methods: make(map[string]map[string]*symbols.Symbol),
structuralMethods: make(map[string][]*symbols.Symbol),
concreteTypes: make(map[string]types.Type),
nominalTraits: make(map[string]types.TraitType),
importsByModule: make(map[string]map[string]bool),
importAliasesByModule: make(map[string]map[string]string),
modulePaths: make(map[*parser.RootNode]string),
functionDefinitions: make(map[*symbols.Symbol]*functionDefinitionInfo),
genericFunctions: make(map[*symbols.Symbol]*genericFunctionInfo),
genericValues: make(map[*symbols.Symbol]*genericValueInfo),
genericAliases: make(map[*symbols.Symbol]*genericAliasInfo),
traitDefaults: make(map[string][]*symbols.Symbol),
definitionTypeBindings: make(map[*symbols.Symbol]map[string]types.Type),
}
a.predefineBuiltins()
for _, name := range []string{"i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64", "isz", "usz", "bool", "str", "cstr"} {
if symbol, ok := a.universe.Resolve(name); ok {
a.concreteTypes["builtin:"+name] = symbol.TypeInfo
}
}
return a
}
func (a *Analyser) Errors() []error {
return a.errors
}
func (a *Analyser) errorf(node parser.Node, format string, args ...any) {
a.errors = append(a.errors, shared.NewError(node.GetLoc(), format, args...))
}
func (a *Analyser) AnalyseModule(root *parser.RootNode, path string, trustedStandardLibrary bool) {
a.DeclareModule(root, path, trustedStandardLibrary)
a.AnalyseModuleBody(root, path)
}
// DeclareModule builds the module-level semantic interface without visiting
// implementation bodies. Keeping this phase explicit is what allows imported
// interfaces to be inspected independently of source ASTs.
func (a *Analyser) DeclareModule(root *parser.RootNode, path string, trustedStandardLibrary bool) {
a.modulePaths[root] = path
// Aliases are module-local; method tables remain available so later modules
// can resolve methods exported by their imports.
if a.aliasesByModule[path] == nil {
a.aliasesByModule[path] = make(map[string]*aliasInfo)
}
a.aliases = a.aliasesByModule[path]
mod := a.modules[path]
if mod == nil {
mod = &symbols.Module{
Name: path,
Scope: symbols.NewScope(a.universe),
TrustedStandardLibrary: trustedStandardLibrary,
}
a.modules[path] = mod
}
a.current = mod.Scope
a.currentMod = path
a.currentTrustedStandardLibrary = trustedStandardLibrary
a.currentImports = a.importsByModule[path]
a.currentImportAliases = a.importAliasesByModule[path]
a.currentRoot = root
if a.currentImports == nil {
a.currentImports = make(map[string]bool)
a.importsByModule[path] = a.currentImports
}
if a.currentImportAliases == nil {
a.currentImportAliases = make(map[string]string)
a.importAliasesByModule[path] = a.currentImportAliases
}
a.collectTopLevel(root)
if path == "builtin" {
for _, name := range []string{"Unit", "Error", "Option", "Result"} {
if symbol, ok := mod.Scope.Symbols[name]; ok {
_ = a.universe.Define(symbol)
}
}
}
}
// AnalyseModuleBody resolves one implementation against the interfaces
// declared so far. Dependencies must have been declared first.
func (a *Analyser) AnalyseModuleBody(root *parser.RootNode, path string) {
mod := a.modules[path]
if mod == nil {
return
}
a.current = mod.Scope
a.currentMod = path
a.currentTrustedStandardLibrary = mod.TrustedStandardLibrary
a.currentImports = a.importsByModule[path]
a.currentImportAliases = a.importAliasesByModule[path]
a.currentRoot = root
a.aliases = a.aliasesByModule[path]
a.resolveBodies(root)
}