KodaScript Semantic Typing Notes
This document is the current implementation-facing contract for KodaScript static typing. It is intentionally lean. It exists to guide compiler/type-checker work without freezing the full language design too early.
Repository boundary:
- pure language/compiler implementation belongs in
kodascript-core/ - server/runtime integration belongs in
server-node/
New language/compiler features should land in kodascript-core first, and only touch server-node when host/runtime integration is actually required.
Scope
This document covers:
- internal compile-time type forms
- assignability rules
- async / await typing
- statically-known API surface used by the semantic checker
- current boundaries of compile-time checking
This document does not define the full language grammar or runtime behavior in exhaustive detail.
Internal Type Forms
The semantic checker currently reasons about these internal type categories:
- Primitive types:
int,long,float,double,bool,char,string,void - Array types:
T[] - API canonical types:
OSApi,FileSystem,Window,Node,FileNode,FolderNode,DriveNode,AppNode,ProcessInfo,UserInfo,GroupInfo,ServiceInfo,Connection,ProbeResult,ComponentInfo - built-in exception object type:
Exception - runtime exception subtype names such as
RuntimeError,TypeError,PermissionError,AuthenticationError,FileSystemError,NetworkError,TimeoutError, andIndexError - User class types: class name such as
Player nullunknownAwaitable<T>for async expressions
Awaitable<T> is compiler-internal only. Players do not write it directly.
Async Typing
Async expressions produce Awaitable<T> at compile time.
Examples:
Device.Connect(...)->Awaitable<Connection>Device.Probe(...)->Awaitable<ProbeResult>os.GetProcesses()->Awaitable<ProcessInfo[]>os.Restart()->Awaitable<bool>term.GetOutput()->Awaitable<string>
Applying await to Awaitable<T> yields T.
Using await on a non-awaitable expression is a compiler error.
Using an Awaitable<T> where T is required is a compiler error.
Examples:
Connection conn = Device.Connect(ip, 22, user, pass); // compile error
Connection conn = await Device.Connect(ip, 22, user, pass); // OK
var conn = Device.Connect(ip, 22, user, pass); // inferred as Awaitable<Connection>
conn.GetFileSystem(); // compile errorAssignability
Current assignability rules:
- exact type matches are allowed
- primitive widening conversions are allowed using existing numeric rules
nullis assignable to reference-like types in current practice- API subtype to API supertype assignment is allowed using the API type registry
- array assignment requires compatible element types
Awaitable<T>is not assignable toT
The checker does not currently model general union types.
Statically-Known API Surface
The semantic checker uses a minimal API signature registry.
Current purpose:
- determine whether a known API/class member exists
- infer return types for known calls
- mark async calls as
Awaitable<T> - reject obvious missing-
awaitmistakes - reject deterministic assignment mismatches such as:
OSApi os = Device.GetOS();
ProcessInfo[] procs = os.GetFileSystem(); // compile errorCurrent non-goals:
- exhaustive argument-type validation for every API method
- full static modeling of all dynamic library exports
- full union/error-string typing
Dynamic library note:
await Device.Load("/path/to/lib.so")is compile-timeunknown- the compiler validates the
Load(...)call boundary only - exported library members remain runtime-driven until the language gains library signatures, headers, or import metadata
Current Compile-Time Guarantees
The compiler/type-checker should catch:
- missing
awaitwhen assigning async results into concrete types - member access on awaitable values before
await - calls to nonexistent methods on statically-known API types
- calls to nonexistent methods on statically-known user class types
- field/property access on statically-known API data objects such as
ProcessInfo,UserInfo,GroupInfo, andServiceInfo - deterministic return-type mismatches from statically-known calls
- statically-known members on caught
Exceptionvalues incatch (Exception ex) - statically-known members on typed caught exception values such as
catch (RuntimeError ex) - catch-filter expressions in
catch (Exception ex) when (...), type-checked in the catch scope
Runtime checks still remain as a backstop.
Exceptions And Catch Filters
Script catches only handle normalized KodaScript runtime exceptions. Unexpected internal engine errors still bypass script catch blocks and terminate the script.
Current exception hierarchy:
ExceptionRuntimeError:ExceptionTypeError:RuntimeErrorPermissionError:RuntimeErrorAuthenticationError:RuntimeErrorFileSystemError:RuntimeErrorNetworkError:RuntimeErrorTimeoutError:RuntimeErrorIndexError:RuntimeError
This means catch (RuntimeError ex) acts as a broad script-runtime catch, while catch (PermissionError ex) only handles permission-class failures.
C#-style catch filters are supported:
try {
int x = 1 / 0;
} catch (Exception ex) when (ex.Message.Contains("Division")) {
Print(ex.Message);
}Filters are evaluated in catch order after type matching. The first catch whose type matches and whose when (...) filter evaluates truthy runs.
Scripts can also throw explicit built-in exception objects:
throw new FileSystemError("File not found", "E_FILE_NOT_FOUND");Current constructor shape:
new Exception(message)new Exception(message, code)- same for
RuntimeError,TypeError,PermissionError,AuthenticationError,FileSystemError,NetworkError,TimeoutError, andIndexError
Errorable Returns
Some APIs intentionally return branchable domain results such as bool | string, object-or-string, or object-or-null.
Current approach:
- keep deterministic compile-time checks strict where the signature is fully known
- keep runtime checks as the backstop for errorable/union-like returns
- allow the signature registry to carry lightweight metadata about errorable calls in preparation for a later union phase
- emit warning
W008when a known errorable call is assigned directly into a concrete declared type without prior branching or checking - emit warning
W009when a previously stored errorable value is later used or assigned as if it were already narrowed - reject user-method return statements at compile time when the declared return type is concrete but the returned expression is still statically known to be nullable or errorable
Current metadata fields in the signature registry:
returnTypemayReturnNullmayReturnErrorStringerrorCodes
This avoids overfitting the language too early while keeping room for a future explicit result/union model.
Example:
OSApi os = Device.GetOS();
bool started = await os.StartService(8080, "/c/home/alice/server.b");StartService() is now success-or-throw rather than bool|string, so it no longer participates in W008 string-risk analysis.
For user-defined method returns, deterministic risk is stricter than ordinary local use:
public async Window Open(OSApi os) {
return await os.OpenNode("/c/secret.txt"); // compile error
}That return expression is still known to be Window | null, so it cannot satisfy a concrete Window return type without narrowing first.
Branch-local narrowing is now recognized for simple checks such as:
var conn = await Device.Connect("10.0.0.5", 22, "admin", "pass");
if (conn != null) {
string name = conn.GetName(); // no W009 in this branch
}Device.Connect(...) remains a nullable API for reachability/service absence, but invalid credentials now throw AuthenticationError instead of being modeled as a nullable-or-string result.
It also recognizes nullability guards for nullable object results:
var win = await os.OpenNode("/c/secret.txt");
if (win != null) {
string title = await win.GetTitle();
}
if (win is Window) {
string title = await win.GetTitle();
}
if (win == null) {
Print("blocked");
} else {
string title = await win.GetTitle();
}Explicit Non-Goals For This Phase
This phase does not fully solve:
- Static member validation for the entire language and runtime
- Tooling/IDE display of inferred
vartypes - Union return types such as
ProcessInfo[] | string - Static typing of dynamically loaded
.solibrary exports - Full overload resolution and argument-type validation everywhere
Phase 2 Direction
The intended next expansion is:
- full member existence validation
- argument count and argument type validation
- return-type assignability across more expression forms
- better nullability rules
- union/error-result modeling where useful
Until then, this document should stay small and track what the compiler actually enforces.