feat(report): write section about generators

This commit is contained in:
HEL
2026-07-20 16:38:43 +02:00
parent ff48a306e5
commit 4fbd4eccca
4 changed files with 12794 additions and 5 deletions
+12735
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -189,4 +189,11 @@ You can also change the order or the names of the sections, for instance, if you
= Python Language Objects
#include-offset("appendices/python_parsing.typ")
#include-offset("appendices/python_parsing.typ")
#pdf.attach(
"../manual.pdf",
relationship: "supplement",
mime-type: "application/pdf",
description: "Midas User Manual"
)
@@ -644,7 +644,7 @@ All implementations of the classes discussed in this section are available in th
== Output <sec:python-output>
We have finally finished implementing our type checker for Python. Although this report skips some parts of the implementation, most of the important areas are covered. Now while type-checking, we generated a number of diagnostics (warning or errors) which can be printed to the user. This is the job of the #acr("CLI"), which we will not discuss here but is available in the repository in #code-ref(<cli>, "midas/cli/"). Also checkout the #fn-link(<manual>, "docs/manual.pdf")[manual] which provides detailed explanation of all #acr("CLI") functionalities.
We have finally finished implementing our type checker for Python. Although this report skips some parts of the implementation, most of the important areas are covered. Now while type-checking, we generated a number of diagnostics (warning or errors) which can be printed to the user. This is the job of the #acr("CLI"), which we will not discuss here but is available in the repository in #code-ref(<cli>, "midas/cli/"). Also checkout the #code-ref(<manual>, "docs/manual.pdf", body: [manual]) which provides detailed explanation of all #acr("CLI") functionalities.
There is still one missing part which we will discuss in @sec:impl-generation to generate the runnable Python code, including any necessary runtime assertions.
Our type checker must then output some information about what it checked and the potential assertions to generate, as for ensuring that two dataframes have the same length.
@@ -1,17 +1,64 @@
#import "../../requirements.typ": isc-hei-bthesis
#import isc-hei-bthesis: todo
#import "../../utils.typ": code-ref
#import "@preview/acrostiche:0.7.0": acr
= Code Generation <sec:impl-generation>
The final stage of compiling a program with Midas is the generation of a runnable file.
This process comes after type checking, which verified the program's soundness and collected some assertions to perform at runtime.
== Stubs <sec:gen-stubs>
#todo[]
As Midas provides its own language to define custom types, the Python interpreter and other type checkers are completely oblivious to their existence and meaning. Although theoretically not necessary for runtime, types may be used while interpreting annotations, cast calls or through reflection. One solution is to generate stubs that can be used in Python code representing custom types defined in Midas.
Simple derived types are translated through class inheritance, type aliases using simple assignments, and generics with Python native `Generic` and `TypeVar` classes. Functions are converted to `Protocol` subclasses for rich structural subtyping with other type checkers.
Obviously, not all features of Midas can be translated into Python. Constraint types for example are simply represented like derived types, omitting the constraint aspect.
The stubs can be manually generated with a #acr("CLI") command, to use when developing, and are automatically output when compiling for runtime use.
The stubs generator implementation is available in the repository in #code-ref(<stubs-gen>, "midas/generator/stubs.py").
== Output Code <sec:gen-code>
#todo[]
Given the `TypedAST` produced by `PythonTyper`, we must then convert `p.Stmt` and `p.Expr` nodes back to Python's `ast.stmt` and `ast.expr`. This process is quite similar to how `PythonParser` handled the opposite situation in @sec:python-parsing. The `Generator` thus implements `p.Stmt.Visitor[ast.stmt]` and `p.Expr.Visitor[ast.expr]`.
In the end, an `ast.Module` is produced and unparsed into raw Python code. The full implementation of `Generator` is available in the repository in #code-ref(<generator>, "midas/generator/generator.py").
== Assertions <sec:gen-assertions>
#todo[]
`Generator` also needs to produce assertions for each `cast` expression, as described in @sec:gen-assertions. Such expressions may appear anywhere in the program, including in other expressions. Because Python assertions are statements, we cannot simply insert them in the surrounding expression. Instead, the approach we implement is to define an alias variable for the expression, before the statement where it appears, add the `assert` statement and insert back the alias where the expression is used.
In practice, this looks like @fig:example-assert-alias.
#figure(
[
*Source*
```python
bar = cast(float, foo)
```
*Generated*
```python
__midas_a0__ = foo
assert isinstance(__midas_a0__, float), f'source.py:L1:7: CastError: Cannot cast {type(__midas_a0__).__name__} to float'
bar = __midas_a0__
del __midas_a0__
```
],
caption: [Example expression alias for runtime assertion]
) <fig:example-assert-alias>
Several aspects must be noted:
- aliases are given unique names with an incremental id.
- assertions are given detailed messages, including the expression's location in the original source file. Indeed, this is necessary to provide users with useful error messages since any runtime `AssertionError` will show the context in the generated file. This is a simple solution which could be improved by specially crafting and raising an exception with a manipulated traceback. However, we must not assume that the source file will always be available at runtime, as users may copy only generated files to another location.
- we also generate a `del` statement right after the last usage of an alias to avoid uselessly polluting the environment.
One limitation is to be mentioned. As described in the #code-ref(<manual>, "docs/manual.pdf", body: [user manual]), aliases are eagerly evaluated before their actual uses meaning logical short-circuiting may be bypassed. Like many languages, Python implements logical `and` and `or` operations as short-circuiting operators, which skip evaluating the second operand if unnecessary.
A tradeoff is also made for members (properties and methods), which are not checked at runtime. This could be one possible future improvement, as discussed in @chap:conclusion.
For constraint expressions, we must generate equivalent Python functions that can be used to verify predicates at runtime. This is handled by the `ConstraintGenerator` class, available in the repository in #code-ref(<constraint-gen>, "midas/generator/constraints.py").
Special care is taken to avoid generating unused functions and only generating one function for each constraint expression.
/*
- Code generation