Type Declaration and Conversion
Alia is a statically and strongly typed language; type checking runs in the Check phase. type describes types, typify derives types from existing objects, class declares a type while defining an object, as performs explicit type conversion, and body-less alias declarations register signatures with the syntax-awareness layer and the compiler.
1. type: Defining Type Descriptions
Basic Syntax
type <Key typeName> <Object typeDescriptionObject>
Example:
type Person *[name: String, age: Number]
type NumberOrString Number | String
type NumberArray [Number]
Type Description Notation
| Notation | Description | Example |
|---|---|---|
A | B | Union type | type NumberOrString Number | String |
[T] | Array type | type NumberArray [Number] |
*[k1: T1, k2: T2] | Object type | type Person *[name: String, age: Number] |
[T, ...] | Variadic array | type DynamicArray [Number, ...] |
*[k: T, ...] | Variadic object | type DynamicObject *[name: String, ...] |
T? | Nullable type | alias greet <String? name> -> { name } |
Once declared, types can be used directly for variable annotations, alias parameters, and return types:
type Person *[name: String, age: Number]
const p: Person = *[name: 'Alice', age: 30]
print p.name
Composite type descriptions can also be written directly at alias parameter positions without naming them first:
alias sum <[Number, ...] nums> -> Number {
let total = 0
each v in nums { total += v }
return total
}
alias getName <*[name: String] person> -> String { return person.name }
Redefining a type with the same name reports TYPE_ALREADY_DEFINED.
2. typify: Creating Types from Existing Objects
Basic Syntax
typify <Key typeName> <Object origObject>
Example:
const obj = *[name: 'Alice', age: 30]
typify Person obj
typify MutablePerson *[name: 'Alice', age: 30, ...]
Semantics
typifygenerates an object description from the inferred type of each property of the source object and registers it as a new type undertypeName.- A trailing
...on the source object creates a mutable (variadic) type that allows properties to be added or removed; otherwise the structure is fixed. - Applying
mutto a scalar type has no effect; the checker emits aTYPE_INVALID_OPERATIONwarning. - Redefining a type with the same name reports
TYPE_ALREADY_DEFINED.
const obj = *[name: 'Alice', age: 30]
typify Person obj
const p: Person = *[name: 'Bob', age: 25]
print p.name
3. The Type Declaration Mechanism of class
Declaring a class with class is equivalent to performing three steps in order: defining the object, running typify, and declaring a constant with the class name.
class Person *[name: '', age: 0]
This is equivalent to:
const Person = *[name: '', age: 0]
typify Person Person
For classes with inheritance, the inheritance expression is evaluated first, then the last two steps run:
class Student extends Person *[studentId: '']
This is equivalent to:
const Student = *[studentId: ''] extends Person
typify Student Student
Rules:
- Class mutability is declared by a trailing
...in the class body (nomutmodifier). - When the class body lacks
new, instantiation produces a copy of the class body object. - The anonymous class
class *[body]omits the class name, is only evaluated as an expression to the class body object, and registers no type or constant.
const Person = *[name: '', age: 0]
typify Person Person
const p: Person = *[name: 'Alia', age: 1]
print p.name
4. as: Explicit Type Conversion
Basic Syntax
<Value anyValue> as <Object typeObj>
Example:
const num = '42' as Number
const str = 42 as String
const bool = 'true' as Boolean
const big = '99999999999999999999' as BigInt
Conversion Mechanism
Resolution proceeds in the following order, returning on the first match:
- If the value is already of the target type, the original value is returned directly.
- Call the value's
to<TargetType>method, such as'42'.toNumber. - Call the target type's
from<SourceType>method. - If none of the above apply, a
RUNTIME_TYPE_ERRORis thrown.
const s = 'hello'
print call s.toArray
print s.length
as is also one of the narrowing tools for <Value>; combined with typeOf or Ok, it can lift TYPE_VALUE_OPERATION restrictions:
alias process <Value data> -> { (data as Number) * 2 }
5. Body-less alias: Signature-Only Declarations
Basic Syntax
alias <Keys pattern>
alias <Keys pattern> -> <Object type>
Omitting the stack body yields a body-less declaration, which only registers the syntax matching pattern and parameter signature without producing a runtime implementation.
alias greet <String name>
alias greet <String name> -> String
Semantics
- The parser registers the pattern, the checker records the parameter and return types and skips body checking, and the formatter omits the stack body when rendering.
- The declaration can be collected by the syntax-awareness layer for completion, go-to-definition, and hover hints.
- The compiler recognizes the signature; the actual implementation is provided on the override side by
@compileAliasor@compileClass. - Invoking the declaration in syntax form or via
callthrows a runtime error indicating that the alias has no runtime action. - A body-less declaration can carry a
-> <Type>return type; the checker adopts that type directly and does not warn about missing returns or unused parameters.
alias greet <String name>
@compileAlias greet <String name> -> js { 'console.log(' .. name .. ')' }
The difference between a full declaration and a body-less declaration:
alias add <Number a> and <Number b> -> Number { a + b }
alias remoteAdd <Number a> and <Number b> -> Number
The former works in both interpretation and compilation; the latter only provides the signature and awaits an implementation from the override side.
Declaration Files (.d.alia)
.d.alia files only carry signature declarations (type descriptions + body-less alias method members + export interface markers); they register signatures when loaded but do not participate in execution:
type StatInfo *[
size: Number,
isFile: Boolean,
isDirectory: Boolean,
modified: String
]
type myfs *[
read: alias <String path> -> String,
stat: alias <String path> -> StatInfo,
exist: alias <String path> -> Boolean
]
export myfs
- Built-in type signatures live in
Base/Core/src/types/*.d.aliaand standard library signatures inBase/Core/libs/*/*.d.alia, imported upfront byBase/Core/lib.alia. - Method members are declared as body-less
aliasentries with strict parameter types and precise return types; nested data shapes are described with companiontypedeclarations for precise member-chain resolution. - Only types marked with
exportexpose method signatures externally (e.g.import 'fs' as fsresolves by module name); companion shape types are used only for structural resolution. - Slots for heterogeneous payloads, callbacks, variadic parameters, and runtime coercions permanently remain
<Value>; passing aValueargument to a precise slot raises an error, whileDynamicarguments skip validation. - Importing a
.d.aliafile at runtime returns an empty namespace and defines no variables;classkeeps its original meaning (define the object, runtypify, declare the constant), and user code redefining an existing type name still reportsTYPE_ALREADY_DEFINED. - When deliberately bypassing static checks to test runtime validation, use the explicit
<Dynamic>type.
6. Generics: <Value> and <Dynamic>
<Value> denotes any type of undetermined content (except code), while <Dynamic> denotes a fully trusted dynamic type. Both relax static checking; <Dynamic> must be declared explicitly.
6.1 <Value>
An unnarrowed <Value> triggers TYPE_VALUE_OPERATION when performing operations that require a concrete type; narrow it first with typeOf / Ok / as:
- Arithmetic operations (
+-*/%) - Logical operations (
&|) and unary operations (!, unary-) - Conditional tests (
if/while/forconditions) eachtargets, index accessvalue[...], and property accessvalue.prop- Comparison against another non-literal value
Legitimate dynamic bridges:
- Comparison against literals:
value == 42,value == 'text' call value.valueOf: unwraps the underlying primitive value and returns the original type
alias process <Value data> -> {
if typeOf data == 'Number' {
data * 2
} else {
data
}
}
6.2 <Dynamic>
<Dynamic> skips all type checking; its operands never trigger TYPE_VALUE_OPERATION.
alias bypass <Dynamic data> -> {
data * 2
}
The Rust backend does not support <Dynamic>; at compile time it emits a warning and falls back to the Value runtime.
7. Index Inference, Assignment Checking, and Type Compatibility
7.1 Index Inference
| Target | Index Type | Inferred Result |
|---|---|---|
Array array[i] | Number | Element type |
String str[i] | Number | String |
Immutable object object['key'] | String | The corresponding property type (when the key is a literal) |
Mutable object object['key'] | String | Value |
const numbers = [1, 2, 3]
const num = numbers[0] ~ inferred as Number
const person = *[name: 'Alice', age: 30]
const name = person['name'] ~ inferred as String
7.2 Assignment Checking
- Variable assignment
name = val: the type ofvalmust be assignable to the declared type ofname, otherwiseTYPE_MISMATCHis reported. - Member assignment
obj.prop = val: the property must exist on the non-variadic object, and the type ofvalmust be assignable to the property type. - Index assignment
arr[i] = val: the array index must be aNumber; assigning to a non-variadic array reportsTYPE_NON_VARIADIC_ARRAY_ASSIGNMENT; variadic arrays validate element type compatibility. - Object index assignment
obj['key'] = val: the key must be aString, and the value type must be compatible with the property type.
7.3 Type Compatibility
| Source → Target | Variadic Array/Object | Non-Variadic Array/Object |
|---|---|---|
| Variadic → | Allowed | Forbidden |
| Non-variadic → | Allowed | Allowed (property-level compatibility) |
A variadic source cannot be assigned to a non-variadic target; a non-variadic source can be assigned to a variadic target. Class instances are compatible with parent class types upward along the extends chain.