Conversation
Prior to this PR the order in which contexts were derived was hardcoded: Every AST element forwarded its context to all its child elements (with possibly some derivation happening). This works well for many type systems, specifically ones that mainly deal with expressions but hits limits when dealing with side-effecting statements. Examples of things that do not work with this system: * We cannot constrain the indexing expression of an array assignment or array indexing based on the array parameter since this requires deriving the context of the expression from the array parameter * We cannot forward data from one statement to another (either forwards or backwards), since these are sibling elements. This PR therefore implements a new mechanism that is a strict superset of functionality from the existing `check*` methods by enabling every subelement to specify which subelements it depends on. The generator then performs a topological sort to generate subelements in some order satisfying the dependencies between contexts. As an example use, the `BitwidthTypeSystem` can now generate inbounds expressions by restricting the bitwidth of the indexing expression to match the dimensions of the array parameter. This is an alternative to #837 which implements the same functionality but in a less powerful and more composable fashion. Specifically, multiple type systems can still be combined into one as long as their dependencies do not create a cycle.
|
I got quite lost in this PR haha How do i read this? Does this dependency array specify any order? |
Yes! Every position in the array corresponds to the subelement we are calculating the context for. The very last position in a |
|
Oh I meant how this dependencyarray specifies the order of AST node generation, for instance: |
The order of AST node generation is implicit through what input dependencies the For example, for your first specification, one possible return DependencyArray<ast::ArrayAssignmentStatement>{
// Depends on the index (index 1 in the SubElements tuple).
/*arrayName=*/Dependency<ast::ArrayAssignmentStatement, 1>([](const Context& context, const ast::Expression& index) {
return ...;
}),
// Depends on the value (index 2 in the SubElements tuple).
/*index=*/Dependency<ast::ArrayAssignmentStatement, 2>(...),
// Depends on nothing.
/*value=*/Dependency<ast::ArrayAssignmentStatement>(...),
...
};One can also specify more than one index. E.g. the array name could also depend on both the value and index and the ordering would be unchanged. The framework performs a topological sort according to these dependencies to generate the AST nodes in that order. An abbreviated example for your second specification would e.g. be: return {
// Only depends on input context.
Dependency<ast::ArrayAssignmentStatement, PARENT_CONTEXT>(...),
// Requires array name + index.
Dependency<ast::ArrayAssignmentStatement, 0, 1>(...),
// Requires only the array name.
Dependency<ast::ArrayAssignmentStatement, 0>(...),
...
}; |
|
I am a bit worried now that configuring it is more complicated than hardcoding the order... |
This approach does have many advantages and I don't think is more complicated than hardcoding the order. Specifically:
Its also less powerful in that it doesn't allow nor make the type system care about generating AST nodes. |
|
Ok, I got this now, i am convinced that this is the way to go about it. As far as i understood, we had: DependencyArray:
Dependency:
I like the separation for requirement (1,) and (2, 3,) now, maybe we could try to improve the readability like this: I like that the template parameters specify the signature of the transfer function: Question: Is there a way to communicate this design consideration with the reader more explicitly? Question: Is there a better name for |
Absolutely, we could just add the indices to e.g.
The return type of the transfer function is always a context. The first argument I tried to add in the documentation of
I agree actually. It feels more imperative than declarative. The key-service is actually the transfer function, the generation order is a side-effect. |
Makes sense
BTW, this reminds me of the
I think either |
|
So it is not debating whether it should be called |
Prior to this PR the order in which contexts were derived was hardcoded: Every AST element forwarded its context to all its child elements (with possibly some derivation happening). This works well for many type systems, specifically ones that mainly deal with expressions but hits limits when dealing with side-effecting statements. Examples of things that do not work with this system:
This PR therefore implements a new mechanism that is a strict superset of functionality from the existing
check*methods by enabling every subelement to specify which subelements it depends on. The generator then performs a topological sort to generate subelements in some order satisfying the dependencies between contexts.As an example use, the
BitwidthTypeSystemcan now generate inbounds expressions by restricting the bitwidth of the indexing expression to match the dimensions of the array parameter.The API is currently only implemented for
BinaryExpressionandArrayReadExpressionbut the goal is the remove the oldcheck*API entirely. This will be done in a follow up PRThis is an alternative to #837 which implements the same functionality but in a less powerful and more composable fashion. Specifically, multiple type systems can still be combined into one as long as their dependencies do not create a cycle.
Note: For formal geeks, this implements Attribute grammars but every AST node currently has the same set of symbols