|
1 | 1 | /**
|
2 | 2 | * Provides shared functionality for computing type inference in QL.
|
3 | 3 | *
|
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. |
6 | 6 | *
|
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 | + * ``` |
10 | 124 | */
|
11 | 125 |
|
12 | 126 | private import codeql.util.Location
|
|
0 commit comments