← Catalog

RFC-002AcceptedLanguage

Type System

Depends: RFC-000RFC-001

Blocks: RFC-004RFC-007RFC-009

1. Abstract

This RFC specifies Aura’s static type system: nominal class/interface types, nullability (T vs T?), generics with monomorphization, subtyping via inheritance and interface implementation, local type inference, overload resolution, flow-sensitive narrowing, and soundness goals.

It assumes the surface from RFC-001 and leaves runtime representation details to RFC-004 / RFC-006.

Toolchain today (2026-07-20): nominal classes/interfaces/struct/enum, monomorphized generics + bounds, local null flow + !! / ?: / ?., type-arg inference, fun types/Ty::Fun + lambdas (val capture MVP). Not yet: inheritance hierarchy, full overloading, structural typing, rich closure captures.

2. Motivation

2.1 Problem statement

A Java-like object model without modern nullability recreates classic NPE classes of bugs. A Go-like GC language without a strong static system shifts defects to production. Aura needs a sound-enough, ergonomic system that matches RFC-000 decisions.

2.2 Why now

Type rules gate compiler architecture, stdlib signatures, and reflection reification policy.

2.3 Success metrics

MetricTarget
SoundnessNo type confusion or null-on-T in safe code without explicit assert/unsafe
InferenceMost locals need no annotation; APIs remain explicitly typed at boundaries
GenericsStdlib collections expressible; compile times acceptable via mono + caching
ErrorsActionable diagnostics with spans and expected/actual types

3. Goals

  • Static type checking as default for all Aura code.
  • Expressive generics without unbounded type-level Turing tar pits (policy budget).
  • Clear, teachable subtyping (nominal + interfaces).
  • First-class nullability and Result integration.
  • Coherent overloading and method resolution rules.

4. Non-goals

  • Full dependent types.
  • Gradual typing / any as a production default (may have dyn later—not v1).
  • Higher-kinded types in v1.
  • Structural typing as the default (optional structural records later).
  • Ownership/borrow types (GC language).

5. Prior art & alternatives

SystemNotesTake
JavaNominal, erasureNominal yes; erasure no for concrete generics
KotlinNullability, smart castsAdopt
C#Generics reified-ish on CLRReification spirit via mono
TypeScriptStructural, unsound holesReject as default
Rust traitsCoherence, monoInterface dispatch + mono data
Hindley–MilnerGlobal inferenceLocal/bidirectional only

6. Design

6.1 Overview

Aura is nominally typed with a single inheritance hierarchy for classes and multiple interface implementation. Subtyping is explicit (extends/implements), not inferred from shape.

code
Nothing  <:  all types          (bottom)
T        <:  T?
ClassS   <:  ClassT             if S extends T (transitively)
ClassC   <:  I                  if C implements I

Any (top for references) exists for rare heterogeneous APIs; prefer generics.

6.2 Type kinds

KindDescription
PrimitivesInt, Long, Float, Double, Bool, Char, Byte, Unit
Class typesUser and stdlib classes; reference semantics
Interface typesExistential-ish receivers; vtable dispatch
Enum typesClosed variants; may be sugar over sealed class hierarchy
Struct typesValue types; no subclassing; copy semantics
NullableT? = T{ null } with restrictions
Type constructorsList<T>, Result<T, E>, function types
NothingBottom; empty type (throw, infinite loop)
AnyTop reference type (use sparingly)

Higher-kinded types: not in v1.
Existentials / opaques: type Handle = opaque Long or package-private class—lean simple opaque alias later.

6.3 Nullability & error types

Nullability

  • T is non-null for reference types. Assigning null is a type error.
  • T? accepts T or null.
  • Dereference / method call on T? requires: safe call ?., explicit check + smart cast, ?:, or !!.
  • Primitives are non-null; use Int? for optional primitives (boxed/nullable representation).

Lattice (simplified): Nothing <: T <: T? <: Any? (details refined in formalization).

Errors

MechanismUse
Result<T, E>Expected failure (parse, I/O not found, validation)
ExceptionsAbnormal control flow; bugs; truly exceptional I/O
Nothing returnthrow expressions

Exceptions are unchecked: function types do not list throws. Documentation/@throws may exist for tooling but is not type-enforced in v1.

Result is a nominal generic sum type in prelude/stdlib:

aura
enum Result<T, E> {
  case Ok(value: T)
  case Err(error: E)
}

6.4 Subtyping & assignability

Assignability S assignable to T when S <: T after nullability adjustment.

Class subtyping: single inheritance; transitive.
Interface subtyping: if I : J, then I <: J. Implementing class is subtype of each interface.
Variance:

PositionDefault
Class type paramsInvariant
Interface type paramsAnnotated out / in (Kotlin-style) allowed
Function paramsContravariant
Function returnsCovariant
ArraysInvariant (no Java-style covariant arrays)

Boxing: primitives convert to/from wrapper types only where defined (minimal implicit; prefer explicit conversions).

6.5 Generics

aura
class Box<T>(var value: T)

interface Repo<T> where T : Entity {
  fun get(id: Id): T?
}

fun <T> identity(x: T): T = x
  • Bounds: T : ClassOrInterface and multiple bounds T : A, B.
  • Where clauses for complex constraints.
  • Associated types: not v1 (use generic params).
  • Specialization: not user-facing v1; compiler may specialize.
  • Const generics: not v1.
  • Implementation: monomorphization for concrete type args at codegen; interface-typed values use vtable / fat pointers as needed (RFC-004).

Reification at runtime: limited—see RFC-009. Prefer TypeToken/reified only if introduced later; v1 reflection may see erased interface views but mono preserves concrete layout in native code.

6.6 Type inference

  • Bidirectional checking: check vs infer modes.
  • Locals: infer from initializer.
  • Generics: infer from arguments and expected type; explicit args when ambiguous.
  • Returns: infer for expression-bodied locals; public API should state return types (style; may be enforced by lint).
  • Lambdas: parameter types from expected functional interface / function type.
  • No global HM inference across modules.

Ambiguity → hard error with candidate list (overloads/generics).

6.7 Interfaces & method resolution

  • Interfaces may declare methods and default implementations.
  • Method resolution: receiver static type → applicable members → most specific override; for interfaces, specificity rules + conflict error if two defaults collide without override.
  • Orphan rule (locked): an implements / interface implementation for class C and interface I must live in the same package as C or the same package as I. No third-party packages may add implementations for foreign class + foreign interface pairs.
  • Marker interfaces: empty interfaces allowed (Sendable-like names if needed—race model may not need them).

6.8 Flow-sensitive typing

After:

aura
if (x is String) {
  // x : String
}
if (x != null) {
  // x : T when x was T?
}

Smart casts apply to stable locals (val) and limited var (not assigned in between). Fields are not smart-cast in place—copy to a local (val t = this.field) or use pattern matching.

Exhaustiveness: match on enums and sealed hierarchies must be complete or include else.

6.9 Type-level programming budget

Allowed: generics, bounds, associated constants no, type aliases, simple conditional types no.
Forbidden in v1: arbitrary Turing-complete type computation, HKTs, full dependent types.

6.10 Interop with reflection (RFC-009)

  • Runtime type tokens for classes that opt into metadata retention.
  • Generic concrete mono instances need not reify all type args unless metadata requested.
  • Casts checked against runtime class metadata when available; otherwise best-effort + unsafe.

6.11 Examples

aura
fun len(s: String?): Int {
  if (s == null) return 0
  return s.length()  // s : String
}

fun <T : Comparable<T>> max(a: T, b: T): T {
  return if (a.compareTo(b) >= 0) a else b
}

fun parseInt(text: String): Result<Int, ParseError> {
  // ...
  return Result.ok(42)
}

6.12 Error model / edge cases

IssueHandling
Occurs check / infinite typesReject
Overload ambiguityError with candidates
Inference explosionComplexity limits; ask for annotations
Unchecked castas may throw CastError; as! / unsafe bypasses
Raw typesNone (no Java raw types)

6.13 Compatibility & migration

  • Adding a method to an interface is a breaking change unless defaulted.
  • Generic variance annotations are part of public ABI of source.
  • Edition may tighten inference or nullability edge cases.

7. Open questions

#QuestionOptionsOwnerStatus
1Orphan/impl coherence exact rulesame package as class or interfaceLangResolved
2Field smart-cast policylocals onlyLangResolved
3Primitive boxing implicit?minimalLangResolved
4Any vs no top typekeepLangResolved — keep Any
5Sealed class syntaxLangResolvedsealed class / sealed interface (Kotlin-like); post-MVP implement
6Variance annotation syntax (out/in)LangResolvedout/in when introduced; default invariant until then

8. Rationale & trade-offs

Nominal typing fits Java-like classes and stable APIs. Nullability as types eliminates an entire defect class at modest annotation cost. Monomorphization favors native perf and simpler GC type layouts at the cost of code size—mitigated by LLVM and incremental builds. Rejecting HKTs and global inference keeps the compiler and mental model implementable for MVP. Unchecked exceptions + Result matches RFC-000 without Java’s checked-exception fatigue.

9. Unresolved / future work

  • Formal soundness proof sketch
  • Variance inference vs annotation-only
  • Gradual/dyn story post-v1
  • Reified generics for reflection ergonomics

10. Security & safety considerations

  • Type confusion via bad casts is a security boundary; prefer checked as.
  • !! and unsafe casts must be auditable.
  • Reflection-based access must respect visibility (RFC-009) or require privilege.

11. Implementation plan (optional)

PhaseScopeExit criteria
T0Nominal core + nullabilityTypecheck samples
T1Generics + interfacesStdlib types expressible
T2Inference polishDX benchmarks

12. References


Changelog

DateAuthorChange
2026-07-16Lock sealed (sealed class/interface) + out/in variance direction
2026-07-16Status → Accepted — Review: nominal/null/generics direction locked; sealed/variance deferred
2026-07-16Note implemented type surface vs deferred
2026-07-15Initial skeleton
2026-07-15Solid draft: nominal, T?, Result, mono generics
2026-07-15Lock orphan rule, smart-cast, Any, boxing