Type System Architecture#
This document outlines the high-level architecture of Jac's native type system, detailing its core components and how they interact with each other.
Core Components#
The native type system consists of several key components that work together throughout the compilation process:
graph TD
A[Type Representations] --> B[Type Environment]
A --> C[Type Resolver]
B <--> C
C --> D[Type Checker]
B --> D
E[Symbol Tables] <--> B
F[AST Nodes] --> C
F <--> D
D --> G[Type Inferencer]
B <--> G
Type Representations#
At the core of the system is a hierarchy of classes representing Jac types. Instead of simple strings, these classes provide rich type information that facilitates operations like:
- Type equivalence checking
- Subtyping relationships
- Type operations (like unions, intersections)
- Parameter validation for generics
classDiagram
JacType <|-- PrimitiveType
JacType <|-- ArchitypeType
JacType <|-- CallableType
JacType <|-- ContainerType
JacType <|-- UnionType
JacType <|-- OptionalType
JacType <|-- TypeVariable
ContainerType <|-- ListType
ContainerType <|-- DictType
ContainerType <|-- TupleType
class JacType {
<<abstract>>
+is_equivalent(other: JacType) bool
+is_subtype_of(super_type: JacType, env: TypeEnvironment) bool
+__str__() str
}
class PrimitiveType {
+name str
}
class ArchitypeType {
+name str
+sym_tab UniScopeNode
+arch_node Architype
}
Type Environment#
The Type Environment manages the typing context as the compiler processes the code. It:
- Tracks variable types within lexical scopes
- Maintains the hierarchy of scopes
- Holds contextual information (current architype, return type expectations)
- Caches type resolutions for performance
classDiagram
TypeEnvironment --> "1..*" Scope
Scope --> "0..1" Scope : parent_scope
class TypeEnvironment {
+scope_stack List~Scope~
+current_architype ArchitypeType
+current_callable_ret_type JacType
+inheritance_cache Dict
+node_to_type_map Dict
+enter_scope()
+exit_scope()
+add_variable(name, type)
+get_variable_type(name)
}
class Scope {
+variables Dict~str, JacType~
+type_vars Dict~str, TypeVariable~
+sym_tab_scope UniScopeNode
+parent_scope Scope
+lookup_var(name, deep)
+define_var(name, type)
}
Type Resolver#
The Type Resolver translates syntactic type representations in the AST into the structured JacType objects. It handles:
- Simple type name resolution
- Generic parameter resolution
- Union and intersection types
- Qualified name resolution (for imported types)
Type Checker#
The Type Checker applies type checking rules to validate operations, assignments, and expressions. It:
- Traverses the AST
- Validates operations against operand types
- Checks assignments for type compatibility
- Verifies function calls against signatures
- Reports type errors with meaningful messages
Type Inferencer#
The Type Inferencer determines types when they aren't explicitly annotated:
- Infers types from initializers
- Propagates type information through operations
- Uses constraint solving for complex cases
- Applies contextual typing based on expected types
Compiler Integration#
flowchart TD
A[Parser] --> B[AST Generation]
B --> C[Symbol Table Construction]
C --> D[Type Annotation Resolution]
D --> E[Type Inference]
E --> F[Type Constraint Generation]
F --> G[Constraint Solving]
G --> H[Type Checking]
H --> I[Code Generation]
subgraph "Native Type System"
D
E
F
G
H
end
The type system is tightly integrated with the compiler:
- After parsing and building initial symbol tables, the Type Annotation Resolution phase resolves explicit type annotations into JacType objects
- Next, the Type Inference phase infers types for expressions and variables without explicit annotations
- The Type Constraint Generation phase generates constraints from operations and control flow
- The Constraint Solving phase resolves these constraints to determine concrete types
- Finally, the Type Checking phase validates all expressions and statements against the resolved types
Error Handling#
Type errors are reported through the compiler's error system with:
- Precise source location information
- Clear error messages explaining the issue
- Suggestions for fixes when possible
- Categorization of errors (e.g., assignment incompatibility, invalid operation)
Design Principles#
The architecture follows these key principles:
- Progressive Resolution: Types are refined in multiple passes rather than requiring everything to be determinable in a single pass
- Clear Separation of Concerns: Each component has well-defined responsibilities
- AST Integration: Type information is attached directly to AST nodes for easy access
- Performance Consideration: Caching mechanisms minimize redundant work
- Extensibility: The system can be extended with new type constructs and rules