Compiler Integration#
This document explains how the native type system integrates with the Jac compiler infrastructure.
Overview#
The type system is tightly integrated with the Jac compiler through multiple passes that work together to analyze, infer, and check types throughout the compilation process.
graph TD
subgraph Parsing
A[Parser] --> B[AST Generation]
end
subgraph "Symbol Resolution"
B --> C[Symbol Table Construction]
C --> D[DefUsePass]
D --> E[InheritancePass]
end
subgraph "Type System"
E --> F[TypeAnnotationResolvePass]
F --> G[TypeInferencePass]
G --> H[TypeConstraintPass]
H --> I[TypeCheckPass]
end
subgraph "Code Generation"
I --> J[PyastGenPass]
J --> K[PyBytecodeGenPass]
end
L[Error Reporting System] --- F
L --- G
L --- H
L --- I
Compiler Passes for Type System#
The type system is implemented as a series of compiler passes that execute in a specific order:
1. TypeAnnotationResolvePass#
This pass resolves explicit type annotations in the AST:
class TypeAnnotationResolvePass(Transform[Module, Module]):
"""Resolves explicit type annotations in the AST."""
def __init__(self, ir_in: Module, prog: JacProgram) -> None:
super().__init__(ir_in, prog)
self.resolver = TypeResolver(self.report_error)
self.env = TypeEnvironment(ir_in, self.resolver)
def visit_HasVar(self, node: uni.HasVar) -> None:
"""Process a variable declaration with type annotation."""
if node.type_tag:
type_annotation = self.resolver.resolve_type_from_ast(node.type_tag, self.env)
node.jac_type = type_annotation
# Also store in symbol table if possible
if node.name.sym_link and node.name.sym_link.decl:
node.name.sym_link.decl.resolved_jac_type = type_annotation
super().visit_HasVar(node)
The TypeAnnotationResolvePass: - Creates a TypeResolver and TypeEnvironment - Traverses the AST to find explicit type annotations - Resolves annotations to JacType instances - Attaches types to AST nodes and symbol table entries
2. TypeInferencePass#
This pass infers types for expressions and variables without explicit annotations:
class TypeInferencePass(Transform[Module, Module]):
"""Infers types for expressions and variables without explicit annotations."""
def __init__(self, ir_in: Module, prog: JacProgram) -> None:
super().__init__(ir_in, prog)
self.resolver = TypeResolver(self.report_error)
self.env = TypeEnvironment(ir_in, self.resolver)
self.constraint_collector = TypeConstraintCollector()
def visit_Assignment(self, node: uni.Assignment) -> None:
"""Process an assignment expression."""
# Visit children first to collect their types
super().visit_Assignment(node)
# Generate a type constraint: rhs_type <: lhs_type
if node.left.jac_type is None:
# Infer from right side if left has no type yet
if node.right.jac_type:
node.left.jac_type = node.right.jac_type
else:
# Otherwise add a constraint
self.constraint_collector.add_subtype_constraint(
node.right, node.left,
f"Assignment at {node.loc.line_no}:{node.loc.col_no}"
)
The TypeInferencePass: - Collects types already determined from annotations - Infers types for expressions from their subexpressions - Infers types for variables from initializers - Generates type constraints for further solving
3. TypeConstraintPass#
This pass collects and solves constraints to determine consistent types:
class TypeConstraintPass(Transform[Module, Module]):
"""Collects and solves type constraints."""
def __init__(self, ir_in: Module, prog: JacProgram) -> None:
super().__init__(ir_in, prog)
self.constraint_solver = TypeConstraintSolver(self.report_error)
def apply(self) -> None:
# Collect all constraints from previous passes
constraints = self.prog.type_constraints
# Solve the constraint system
resolved_types = self.constraint_solver.solve(constraints)
# Apply the resolved types to AST nodes
for node_id, resolved_type in resolved_types.items():
node = self.prog.ast_node_map.get(node_id)
if node:
node.jac_type = resolved_type
The TypeConstraintPass: - Takes constraints from the TypeInferencePass - Builds a constraint graph - Applies constraint propagation algorithms - Resolves type variables to concrete types - Updates AST nodes with the resolved types
4. TypeCheckPass#
This pass validates that operations are type-safe:
class TypeCheckPass(Transform[Module, Module]):
"""Validates that operations are type-safe."""
def __init__(self, ir_in: Module, prog: JacProgram) -> None:
super().__init__(ir_in, prog)
self.resolver = TypeResolver(self.report_error)
self.env = TypeEnvironment(ir_in, self.resolver)
def visit_BinaryExpr(self, node: uni.BinaryExpr) -> None:
"""Check a binary expression."""
super().visit_BinaryExpr(node)
left_type = node.left.jac_type
right_type = node.right.jac_type
if not left_type or not right_type:
return # Skip if types are missing (error reported elsewhere)
if node.op.name == uni.Tok.PLUS:
# Check if + operation is valid for these types
if not self.is_valid_addition(left_type, right_type):
self.report_error(
JacErrorCode.TYPE_ERROR_INVALID_OPERATION,
args={
"op": "+",
"left_type": str(left_type),
"right_type": str(right_type)
},
node_override=node
)
else:
# Set the result type
node.jac_type = self.determine_addition_result_type(left_type, right_type)
The TypeCheckPass: - Validates that operations are performed on compatible types - Checks that assignments respect the type system - Verifies function arguments match parameter types - Reports detailed type errors when violations occur - Sets result types for expressions
Integration with Symbol Tables#
The type system heavily integrates with Jac's symbol table system:
graph TD
A[AST Node] --> B[Symbol Table Entry]
A --> C[JacType]
B --> D[resolved_jac_type]
E[TypeEnvironment] --> F[Current Scope]
F --> G[Symbol Table Scope]
F --> H[Variable Type Map]
I[ArchitypeType] --> J[Symbol Table of Architype]
Key integration points: 1. Symbol Table Linking: The Symbol Table helps the type system locate definitions for type names 2. Type Storage: Types are stored both on AST nodes and in Symbol Table entries 3. Scope Navigation: The TypeEnvironment uses Symbol Table scopes to resolve names 4. Member Lookup: ArchitypeType instances maintain links to their Symbol Tables for member lookup
Replacing MyPy Integration#
The native type system completely replaces the previous MyPy-based approach:
Old MyPy Approach | New Native Approach |
---|---|
Convert Jac AST to Python AST | Work directly on Jac AST |
Run MyPy on Python AST | Run native type checking passes |
Extract type strings from MyPy | Use rich JacType instances |
Map types back to Jac AST | Types directly attached to AST nodes |
Limited understanding of Jac constructs | Full understanding of Jac-specific features |
graph TD
subgraph "Old Approach"
A1[Jac AST] --> B1[Python AST]
B1 --> C1[MyPy Analysis]
C1 --> D1[String Type Info]
D1 --> E1[FuseTypeInfoPass]
E1 --> F1[Type String on AST]
end
subgraph "New Approach"
A2[Jac AST] --> B2[Symbol Tables]
B2 --> C2[TypeAnnotationResolvePass]
C2 --> D2[TypeInferencePass]
D2 --> E2[TypeCheckPass]
E2 --> F2[JacType Objects on AST]
end
Integration with Error Reporting#
Type errors are reported through the compiler's existing error reporting system:
def report_error(self, error_code: JacErrorCode, args: dict, node_override: Optional[uni.UniNode] = None) -> None:
"""Report a type error through the compiler's error system."""
loc = node_override.loc if node_override else self.curr_node.loc if self.curr_node else None
self.errors_had.append(
Alert(
error_code=error_code,
msg_args=args,
severity=Severity.ERROR,
loc_override=loc
)
)
Error messages provide: - Clear description of the type error - Exact source location - Expected vs. actual types - Suggestions for fixing the error when possible
Integration with Runtime Type Information#
The native type system can also generate runtime type information when needed:
class PyastGenPass(Transform[Module, Module]):
"""Generates Python AST for code generation."""
def generate_type_annotation(self, jac_type: JacType) -> Optional[py_ast.expr]:
"""Convert a JacType to a Python type annotation."""
if isinstance(jac_type, PrimitiveType):
return py_ast.Name(id=jac_type.name, ctx=py_ast.Load())
elif isinstance(jac_type, ListType):
return py_ast.Subscript(
value=py_ast.Name(id='list', ctx=py_ast.Load()),
slice=py_ast.Index(value=self.generate_type_annotation(jac_type.element_type)),
ctx=py_ast.Load()
)
# ... other type conversions
This enables: - Generation of Python type annotations for Jac code - Runtime type checks if desired - IDE integration and tooling support
Extension Points#
The design allows for easy extension with new types and features:
- Adding New Types: Extend the JacType hierarchy with new type classes
- Custom Type Rules: Modify TypeCheckPass to handle special typing rules
- Language Feature Support: Add specific handling for Jac features like walkers
- Optimization Hints: Use type information for optimization
- IDE Integration: Export type information for IDE tooling
Performance Considerations#
The system includes several optimizations to ensure good performance:
- Type Caching: Resolved types are cached to avoid redundant computation
- Incremental Analysis: Only re-analyze parts of the code that have changed
- Early Error Detection: Fail fast when errors are detected
- Parallel Processing: Some passes can be parallelized for large projects