-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reworked the infrastructure for validation rules and inference rules #64
base: main
Are you sure you want to change the base?
Conversation
…Stateless | ValidationRuleWithBeforeAfter
…in Typir-Langium as well
…uage keys to improve performance
…itrary number of type-safe inference rules (for Functions, operators, classes, primitives), inference made TypeSelector type-safe
…tion for primitive types, fixed imports
…with `validateArgumentsOfFunctionCalls` inside inference rules, moved validation into its own file, fixed bug, more comments
…ference, prevent duplicates during registration
…tion rules, some test cases, fixed various bugs, improved TypeScript type-safety
…rence rule is removed
…to be given to the new ValidationProblemAcceptor now
…or better performance)
…oit language keys, combined validations for arguments matching signatures and for specific calls), renamings
…dation rules (these features are not yet used)
…to a new file; fixed bug
…he properties `filter` and `match` were ignored
… wrote test cases
…e rules of calls of (overloaded) operators
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much @JohannesMeierSE this is not only a lot of work but also (again) very well described for review. I really appreciate the main contributions of the PR very much, especially the introduction of language keys and the chain notation. This should not only improve performance but also readability. I also like the possibility to create the predefined validations with the factory API.
I have hesitated with a general approval a lot though, as I was not 100% sure about the <LanguageType = unknown> additions. I really appreciate the benefit, but to see the changes overall with the additional generics is quite overwhelming. In the end I think I am OK with it.
I am a bit irritated by the discovery noted in your TODO review in line 19, of the class.test.ts and have no ready answer, I think this should be investigated further.
I added some detailed comments.
Regarding test coverage I had no idea for further tests at the moment but they can be added when something comes to mind.
- Associate inference rules with language keys for an improved performance | ||
- Typir-Langium: new API to register inference rules to the `$type` of the `AstNode` to validate, | ||
e.g. `addInferenceRulesForAstNodes({ MemberCall: <InferenceRule1>, VariableDeclaration: <InferenceRule2>, ...})`, see (L)OX for some examples | ||
- Thanks to the new chaining API for defining types (see corresponding breaking changes below), they can be annotated in TypeScript-type-safe way with multiple inference rules for the same purpose. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is unclear to me at this point, what "for the same purpose" refers to (the point before)?
- Typir-Langium: new API to register inference rules to the `$type` of the `AstNode` to validate, | ||
e.g. `addInferenceRulesForAstNodes({ MemberCall: <InferenceRule1>, VariableDeclaration: <InferenceRule2>, ...})`, see (L)OX for some examples | ||
- Thanks to the new chaining API for defining types (see corresponding breaking changes below), they can be annotated in TypeScript-type-safe way with multiple inference rules for the same purpose. | ||
- Provide new `expectValidationHints()` utility for developers to ease the writing of test cases for Typir-based type systems. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering about the naming 'Hints' a bit. Should we maybe stick closer to the langium terminology?
|
||
- Clear the cache for inferred types, when an inference rule is removed. | ||
- Remove removed functions from its internal storage in `FunctionKind`. | ||
- Update the returned function type during a performance optimization, when adding or removing some signatures of overloaded functions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear to me while just reading the Changelog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line 42
(node: unknown) => isTypeReference(node) && node.primitive === 'boolean' | ||
]}); | ||
const typeBool = this.typir.factory.Primitives.create({ primitiveName: 'boolean' }) | ||
.inferenceRule({ languageKey: BooleanLiteral }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this new api in general, it also improves readability imho
// .inferenceRule({ filter: isBooleanLiteral }) // this is the alternative solution | ||
.inferenceRule({ languageKey: TypeReference, matching: (node: TypeReference) => node.primitive === 'boolean' }) // this is the more performant notation | ||
// .inferenceRule({ filter: isTypeReference, matching: node => node.primitive === 'boolean' }) // this is the "easier" notation | ||
.finish(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: always remember to finish. This is a little downside, but fine for me.
* in particular, to support overloaded functions. | ||
* In each type system, exactly one instance of this class is stored by the FunctionKind. | ||
*/ | ||
// TODO review: better name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like a name that shows that it handles more that one function and describes better, what it does, like AvailableFunctionsManager. That would also hint to the overload tasks.
|
||
beforeValidation(_languageRoot: LanguageType, _accept: ValidationProblemAcceptor<LanguageType>, _typir: TypirServices<LanguageType>): void { | ||
// do nothing | ||
// TODO review: Here ValidationRuleStateless is enough, but since it is a function type (and no interface type), it is not possible to implement it here in this class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a new minimal interface here?
export interface ValidationMessageDetails { | ||
languageNode: unknown; | ||
export interface ValidationMessageDetails<LanguageType = unknown, T extends LanguageType = LanguageType> { | ||
languageNode: T; // TODO review: in OX/LOX, 'unknown' instead of 'AstNode' is inferred by TypeScript, why? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be investigated more deeply independently of the review, I do not know out of the box.
export interface InferCurrentTypeRule<TypeType extends Type = Type, LanguageType = unknown, T extends LanguageType = LanguageType> { | ||
languageKey?: string | string[]; | ||
filter?: (languageNode: LanguageType) => languageNode is T; | ||
matching?: (languageNode: T) => boolean; // TODO review: Should we provide "typeToInfer: TypeType" as an additional property here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have reasons against this? Nothing comes to my mind spontaneously.
.inferenceRulesForFieldAccess({ | ||
filter: node => node instanceof ClassFieldAccess, | ||
matching: node => { | ||
const varType = typir.Inference.inferType(node.classVariable); // TODO review: doing type inference on your own here feels a bit strange |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we maybe discuss this in the next meeting? I am not sure I really understand this.
Main motivation for this PR is to improve the performance of validation and inference rules:
undefined
remains as a fall-back solutionBeyond that, this PR contributes:
LanguageService
<LanguageType = unknown>
in order to get rid ofunknown
in the source code. Benefit: Intypir-langium
projects, we work everywhere withAstNode
(instead ofunknown
). In our internal test projects, we have nowTestLanguageNode
instead ofunknown
. Downside: The additional generics in the code make reading/writing the source code slower.typir.factory.Primitives.create({ ... })
) is extended with a chaining API to register inference rules which are dedicated for the currently created type (e.g.typir.factory.Primitives.create({...}).inferenceRule({...}).finish()
): This makes the definition of inference rules more (TypeScript-)type-safe and allows to define an arbitrary number of these rules.typir.factory.Functions.createUniqueFunctionValidation()
ValidationProblem
s, they need to be given to theValidationProblemAcceptor
now, which is provided as additional argument inside validation rules (strongly inspired by Langium!)Suggestions for the review:
CHANGELOG.md
to get an overview.CHANGELOG.md
is complete.TODO review
in the source code to see some more interesting points to discuss during the review.