fix(report): tweak layout

This commit is contained in:
HEL
2026-07-23 21:27:58 +02:00
parent 520148babf
commit 56280ee009
3 changed files with 8 additions and 9 deletions
@@ -60,6 +60,7 @@ These types are implemented by the classes in @fig:types-generics.
INVARIANT = "INVARIANT"
COVARIANT = "COVARIANT"
CONTRAVARIANT = "CONTRAVARIANT"
class TypeVar:
name: str
bound: Optional[Type]
@@ -81,7 +82,7 @@ These types are implemented by the classes in @fig:types-generics.
As noted in the theory chapter (@chap:theory), functions in Python are complex and highly flexible.
A function type thus have a parameter specification and a return type. That parameter specification contains three kinds of parameters: positional-only (`pos`), keyword-only (`kw`) and mixed (`mixed`).
@fig:types-functions shows the implemented structures to handle function types and their parameters.
For simplicity, each parameter will store both its position and name, as well as its type and a flag indicating whether it is required or not (e.g. parameters with default values are optional). You may notice an additional `unsupported` flag in @fig:types-functions:10, which is added for some dataframe-related methods. More information shall be given in @sec:impl-python #todo[replace ref].
For simplicity, each parameter will store both its position and name, as well as its type and a flag indicating whether it is required or not (e.g. parameters with default values are optional). You may notice an additional `unsupported` flag in @fig:types-functions:10, which is added for some dataframe-related methods.
Finally, we will also define a structure to hold multiple signatures of an overloaded function because it cannot be represented as a single `Function`. You may also notice that `OverloadedFunction.overloads` does not store a list of `Function` but simply a list of `Type`, in @fig:types-functions:16. This allows the use of generic functions, which are `Function` types wrapped inside a `GenericType`.
@@ -268,10 +268,8 @@ Additionally, if parameters are given, they are processed and transformed into `
params: list[TypeVar] = self._resolve_type_params(stmt.params)
type: Type = stmt.type.accept(self)
if len(params) != 0:
type = GenericType(name=name, params=params, body=type)
else:
type = DerivedType(name=name, type=type)
if len(params) != 0: type = GenericType(name=name, params=params, body=type)
else: type = DerivedType(name=name, type=type)
try:
self.types.define_type(name, type)
except ValueError:
@@ -307,7 +305,6 @@ When processing a predicate definition, we first need to gather all parameters a
self._predicate_params[param.name.lexeme] = param.type.accept(self)
type: Type = self.type_of(stmt.body)
params: list[ParamSpec] = [self._visit_param_spec(spec) for spec in stmt.params]
if not self._is_valid_predicate(type):
self.reporter.error(
stmt.body.location,
@@ -413,7 +410,9 @@ By polarity, we mean that:
- any _producer_ position, such as a function return type, is considered *positive*
- any _consumer_ position, such as a function parameter, is considered *negative*
Algorithmically, when inferring the variance of a variable, we start at the root generic type with a positive polarity. The inference function identifies all possible positions (type body and members) and recurses with the current polarity multiplied by the position's polarity. For example, if the current polarity is positive and a variable is used in a producer position, the resulting polarity used when recursing is $+ times + = +$. If however the variable is used in a consumer position, the polarity becomes $+ times - = -$. A consumer or negative position basically _flips_ the current polarity. When the recursion reaches the bottom usage, i.e. a simple `TypeVar`, the polarity is recorded.
Algorithmically, when inferring the variance of a variable, we start at the root generic type with a positive polarity. The inference function identifies all possible positions (type body and members) and recurses with the current polarity multiplied by the position's polarity.
For example, if the current polarity is positive and a variable is used in a producer position, the resulting polarity used when recursing is $+ times + = +$. If however the variable is used in a consumer position, the polarity becomes $+ times - = -$. A consumer or negative position basically _flips_ the current polarity. When the recursion reaches the bottom usage, i.e. a simple `TypeVar`, the polarity is recorded.
This algorithm is implemented by the `VarianceInferrer` class in @fig:midas-variance-inferrer-walk (omitted parts of the code are listed in @fig:midas-variance-inferrer).
#codly(
@@ -219,7 +219,6 @@ Similarly to how we handled variable assignments in `if` branches (see @fig:reso
```python
def visit_if_stmt(self, stmt: p.IfStmt) -> None:
test_type: Type = self.type_of(stmt.test)
if (
not self.is_subtype(test_type, self.types.get_type("bool"))
and test_type != UnknownType()
@@ -381,7 +380,7 @@ The simple case where the callee is a `Function` involves matching call-site arg
caption: [Call Dispatcher: call to `Function`]
) <fig:dispatcher-match-func>
Some other simple cases mentioned above can be handled with recursion. Calls to `UnknownType` are also allowed by the type checker, only resulting in an `UnknownType` too. These form the three simple match-cases in @fig:dispatcher-match-simple.
Some other simple cases mentioned above can be handled with recursion, as in @fig:dispatcher-match-simple. Calls to `UnknownType` are also allowed by the type checker, only resulting in an `UnknownType` too.
#codly(
range: (25, 34),