Skip to content

Commit 70c3ae4

Browse files
authored
Merge pull request #19727 from hvitved/shared/type-inference-doc
Shared: Add elaborate QL doc to `TypeInference.qll`
2 parents 28ae396 + 1ec3760 commit 70c3ae4

File tree

1 file changed

+119
-5
lines changed

1 file changed

+119
-5
lines changed

shared/typeinference/codeql/typeinference/internal/TypeInference.qll

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,126 @@
11
/**
22
* Provides shared functionality for computing type inference in QL.
33
*
4-
* The code examples in this file use C# syntax, but the concepts should
5-
* carry over to other languages as well.
4+
* The code examples in this file use C# syntax, but the concepts should carry
5+
* over to other languages as well.
66
*
7-
* The library is initialized in two phases: `Make1`, which constructs
8-
* the `TypePath` type, and `Make2`, which (using `TypePath` in the input
9-
* signature) constructs the `Matching` module.
7+
* The library is initialized in two phases: `Make1`, which constructs the
8+
* `TypePath` type, and `Make2`, which (using `TypePath` in the input signature)
9+
* constructs the `Matching` and `IsInstantiationOf` modules.
10+
*
11+
* The intended use of this library is to define a predicate
12+
*
13+
* ```ql
14+
* Type inferType(AstNode n, TypePath path)
15+
* ```
16+
*
17+
* for recursively inferring the type-path-indexed types of AST nodes. For example,
18+
* one may have a base case for literals like
19+
*
20+
* ```ql
21+
* Type inferType(AstNode n, TypePath path) {
22+
* ...
23+
* n instanceof IntegerLiteral and
24+
* result instanceof IntType and
25+
* path.isEmpty()
26+
* ...
27+
* }
28+
* ```
29+
*
30+
* and recursive cases for local variables like
31+
*
32+
* ```ql
33+
* Type inferType(AstNode n, TypePath path) {
34+
* ...
35+
* exists(LocalVariable v |
36+
* // propagate type information from the initializer to any access
37+
* n = v.getAnAccess() and
38+
* result = inferType(v.getInitializer(), path)
39+
* or
40+
* // propagate type information from any access back to the initializer; note
41+
* // that this case may not be relevant for all languages, but e.g. in Rust
42+
* // it is
43+
* n = v.getInitializer() and
44+
* result = inferType(v.getAnAccess(), path)
45+
* )
46+
* ...
47+
* }
48+
* ```
49+
*
50+
* The `Matching` module is used when an AST node references a potentially generic
51+
* declaration, where the type of the node depends on the type of some of its sub
52+
* nodes. For example, if we have a generic method like `T Identity<T>(T t)`, then
53+
* the type of `Identity(42)` should be `int`, while the type of `Identity("foo")`
54+
* should be `string`; in both cases it should _not_ be `T`.
55+
*
56+
* In order to infer the type of method calls, one would define something like
57+
*
58+
* ```ql
59+
* private module MethodCallMatchingInput implements MatchingInputSig {
60+
* private newtype TDeclarationPosition =
61+
* TSelfDeclarationPosition() or
62+
* TPositionalDeclarationPosition(int pos) { ... } or
63+
* TReturnDeclarationPosition()
64+
*
65+
* // A position inside a method with a declared type.
66+
* class DeclarationPosition extends TDeclarationPosition {
67+
* ...
68+
* }
69+
*
70+
* class Declaration extends MethodCall {
71+
* // Gets a type parameter at `tppos` belonging to this method.
72+
* //
73+
* // For example, if this method is `T Identity<T>(T t)`, then `T`
74+
* // is at position `0`.
75+
* TypeParameter getTypeParameter(TypeParameterPosition tppos) { ... }
76+
*
77+
* // Gets the declared type of this method at `dpos` and `path`.
78+
* //
79+
* // For example, if this method is `T Identity<T>(T t)`, then both the
80+
* // the return type and parameter position `0` is `T` with `path.isEmpty()`.
81+
* Type getDeclaredType(DeclarationPosition dpos, TypePath path) { ... }
82+
* }
83+
*
84+
* // A position inside a method call with an inferred type
85+
* class AccessPosition = DeclarationPosition;
86+
*
87+
* class Access extends MethodCall {
88+
* AstNode getNodeAt(AccessPosition apos) { ... }
89+
*
90+
* // Gets the inferred type of the node at `apos` and `path`.
91+
* //
92+
* // For example, if this method call is `Identity(42)`, then the type
93+
* // at argument position `0` is `int` with `path.isEmpty()"`.
94+
* Type getInferredType(AccessPosition apos, TypePath path) {
95+
* result = inferType(this.getNodeAt(apos), path)
96+
* }
97+
*
98+
* // Gets the method that this method call resolves to.
99+
* //
100+
* // This will typically be defined in mutual recursion with the `inferType`
101+
* // predicate, as we need to know the type of the receiver in order to
102+
* // resolve calls to instance methods.
103+
* Declaration getTarget() { ... }
104+
* }
105+
*
106+
* predicate accessDeclarationPositionMatch(AccessPosition apos, DeclarationPosition dpos) {
107+
* apos = dpos
108+
* }
109+
* }
110+
*
111+
* private module MethodCallMatching = Matching<MethodCallMatchingInput>;
112+
*
113+
* Type inferType(AstNode n, TypePath path) {
114+
* ...
115+
* exists(MethodCall mc, MethodCallMatchingInput::AccessPosition apos |
116+
* // Some languages may want to restrict `apos` to be the return position, but in
117+
* // e.g. Rust type information can flow out of all positions
118+
* n = a.getNodeAt(apos) and
119+
* result = MethodCallMatching::inferAccessType(a, apos, path)
120+
* )
121+
* ...
122+
* }
123+
* ```
10124
*/
11125

12126
private import codeql.util.Location

0 commit comments

Comments
 (0)