Skip to main content

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​

NotationDescriptionExample
A | BUnion typetype NumberOrString Number | String
[T]Array typetype NumberArray [Number]
*[k1: T1, k2: T2]Object typetype Person *[name: String, age: Number]
[T, ...]Variadic arraytype DynamicArray [Number, ...]
*[k: T, ...]Variadic objecttype DynamicObject *[name: String, ...]
T?Nullable typealias 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​

  • typify generates an object description from the inferred type of each property of the source object and registers it as a new type under typeName.
  • 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 mut to a scalar type has no effect; the checker emits a TYPE_INVALID_OPERATION warning.
  • 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 (no mut modifier).
  • 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:

  1. If the value is already of the target type, the original value is returned directly.
  2. Call the value's to<TargetType> method, such as '42'.toNumber.
  3. Call the target type's from<SourceType> method.
  4. If none of the above apply, a RUNTIME_TYPE_ERROR is 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 @compileAlias or @compileClass.
  • Invoking the declaration in syntax form or via call throws 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.alia and standard library signatures in Base/Core/libs/*/*.d.alia, imported upfront by Base/Core/lib.alia.
  • Method members are declared as body-less alias entries with strict parameter types and precise return types; nested data shapes are described with companion type declarations for precise member-chain resolution.
  • Only types marked with export expose method signatures externally (e.g. import 'fs' as fs resolves 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 a Value argument to a precise slot raises an error, while Dynamic arguments skip validation.
  • Importing a .d.alia file at runtime returns an empty namespace and defines no variables; class keeps its original meaning (define the object, run typify, declare the constant), and user code redefining an existing type name still reports TYPE_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 / for conditions)
  • each targets, index access value[...], and property access value.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​

TargetIndex TypeInferred Result
Array array[i]NumberElement type
String str[i]NumberString
Immutable object object['key']StringThe corresponding property type (when the key is a literal)
Mutable object object['key']StringValue
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 of val must be assignable to the declared type of name, otherwise TYPE_MISMATCH is reported.
  • Member assignment obj.prop = val: the property must exist on the non-variadic object, and the type of val must be assignable to the property type.
  • Index assignment arr[i] = val: the array index must be a Number; assigning to a non-variadic array reports TYPE_NON_VARIADIC_ARRAY_ASSIGNMENT; variadic arrays validate element type compatibility.
  • Object index assignment obj['key'] = val: the key must be a String, and the value type must be compatible with the property type.

7.3 Type Compatibility​

Source → TargetVariadic Array/ObjectNon-Variadic Array/Object
Variadic →AllowedForbidden
Non-variadic →AllowedAllowed (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.

Next Steps​