Conversation
| private import TypeMention | ||
| private import TypeInference | ||
| private import TypeInference::Consistency as Consistency | ||
| import TypeInference::Consistency |
9adf294 to
bbc8b9a
Compare
e71b6f6 to
5886197
Compare
| * parameter. | ||
| */ | ||
| bindingset[t, arity, i] | ||
| abstract predicate functionInvokeSignature(Type t, int arity, int i, int j, TypePath path); |
5886197 to
b2b2531
Compare
ab9a603 to
76692f4
Compare
76692f4 to
c5db7b1
Compare
fe9c6eb to
ebbac0e
Compare
| } | ||
|
|
||
| /** Provides predicates for debugging the type inference implementation. */ | ||
| private module Debug { |
| /** Provides predicates for debugging the type inference implementation. */ | ||
| private module Debug { | ||
| AstNode getRelevantNode() { | ||
| exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | |
| /** Provides predicates for debugging the type inference implementation. */ | ||
| private module Debug { | ||
| AstNode getRelevantNode() { | ||
| exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | |
| /** Provides predicates for debugging the type inference implementation. */ | ||
| private module Debug { | ||
| AstNode getRelevantNode() { | ||
| exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | |
ebbac0e to
228fe9c
Compare
228fe9c to
a159ea5
Compare
e58e192 to
8d76018
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Four unresolved moderate issues can produce incorrect inferred types, call targets, and dataflow edges.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Introduces initial Swift type inference for the unified CodeQL library and integrates inferred call targets with dataflow.
Changes:
- Adds type representations, inference plugins, built-in models, and consistency checks.
- Supports generics, tuples, closures, protocols, aliases, fields, and call resolution.
- Adds extensive inference and dataflow tests.
| File | Review |
|---|---|
unified/tools/builtins/types.swift |
Adds modeled built-in operations. |
unified/ql/test/library-tests/type-inference/type-inference.qlref |
Registers inference tests and postprocessing. |
unified/ql/test/library-tests/type-inference/type-inference.ql |
Defines inference and resolution assertions. |
unified/ql/test/library-tests/type-inference/type_constraints.swift |
Tests generic constraints. |
unified/ql/test/library-tests/type-inference/type_alias.swift |
Tests generic type aliases. |
unified/ql/test/library-tests/type-inference/tuples.swift |
Tests tuple inference. |
unified/ql/test/library-tests/type-inference/protocols.swift |
Tests protocols and conformances. |
unified/ql/test/library-tests/type-inference/pattern_matching.swift |
Tests pattern-bound types. |
unified/ql/test/library-tests/type-inference/overload_resolution.swift |
Tests overload resolution. |
unified/ql/test/library-tests/type-inference/lub.swift |
Tests least-upper-bound inference. |
unified/ql/test/library-tests/type-inference/key_paths.swift |
Covers key-path scenarios. |
unified/ql/test/library-tests/type-inference/generics.swift |
Tests generic declarations and calls. |
unified/ql/test/library-tests/type-inference/fields.swift |
Tests field resolution. |
unified/ql/test/library-tests/type-inference/CONSISTENCY/CfgConsistency.expected |
Updates consistency expectations. |
unified/ql/test/library-tests/type-inference/closures.swift |
Tests closure typing and invocation. |
unified/ql/test/library-tests/type-inference/classes.swift |
Tests classes, inheritance, and methods. |
unified/ql/test/library-tests/type-inference/basics.swift |
Tests foundational inference behavior. |
unified/ql/test/library-tests/dataflow/test.expected |
Updates dataflow expectations. |
unified/ql/test/library-tests/dataflow/calls.swift |
Adds resolved-call flow cases. |
unified/ql/test/library-tests/BasicTest/test.ql |
Filters built-in nodes from tests. |
unified/ql/lib/utils/test/TestUtils.qll |
Adds tuple and callable naming helpers. |
unified/ql/lib/utils/test/ExternalLocationPostProcessing.ql |
Normalizes external type locations. |
unified/ql/lib/qlpack.yml |
Adds the type-inference dependency. |
unified/ql/lib/codeql/unified/internal/typeinference/TypeMention.qll |
Models type mentions and aliases. |
unified/ql/lib/codeql/unified/internal/typeinference/TypeInferencePluginSwift.qll |
Moderate (4 votes): Binary expressions incorrectly inherit the common operand type, including comparison and equality expressions whose result should be Bool; result-changing operators must be handled separately without guessing for custom operators. |
unified/ql/lib/codeql/unified/internal/typeinference/TypeInferencePlugin.qll |
Defines language-plugin interfaces. |
unified/ql/lib/codeql/unified/internal/typeinference/TypeInferenceConsistency.qll |
Exposes inference consistency checks. |
unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll |
Moderate (1 vote): Function-valued field invocation remains a target when a direct callable member exists, creating incorrect call and dataflow edges; suppress that branch when a direct target is found. |
unified/ql/lib/codeql/unified/internal/typeinference/TypeAbstraction.qll |
Models generic type abstractions. |
unified/ql/lib/codeql/unified/internal/typeinference/Type.qll |
Moderate (1 vote): TypeParameterType.getDeclaringItem() only handles class-like declarations, omitting function and type-alias parameters; return the actual enclosing declaration. |
unified/ql/lib/codeql/unified/internal/FacadeAst.qll |
Adds tuple and closure helpers. |
unified/ql/lib/codeql/unified/internal/ExprPositionsPluginSwift.qll |
Adds Swift type-context handling. |
unified/ql/lib/codeql/unified/internal/ExprPositionsPlugin.qll |
Defines type-context plugin hooks. |
unified/ql/lib/codeql/unified/internal/ExprPositions.qll |
Expands type-context recognition. |
unified/ql/lib/codeql/unified/internal/dataflow/CallGraph.qll |
Moderate (1 vote): Overload-agnostic targets are wired directly into dataflow, permitting flow through unselected same-name overloads; require compatible or unique target filtering. |
unified/ql/consistency-queries/TypeInferenceConsistency.ql |
Registers inference diagnostics. |
shared/typeinference/codeql/typeinference/internal/TypeInference.qll |
Adjusts shared inference propagation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
8d76018 to
5fd9af6
Compare
5fd9af6 to
0c4c11a
Compare
0c4c11a to
fa6d1e4
Compare
asgerf
left a comment
There was a problem hiding this comment.
Just some observations so far. Will take another look on Monday.
It's quite hard to review the instantiation honestly. It's a collection of plausible-sounding AST class adapters. I have no idea how much of it works.
I'm a little worried about what it would take to support destructuring, as the Declaration interface seems unable to cope with it.
| or | ||
| expr.getParent() instanceof TypeConstraint | ||
| or | ||
| exists(Identifier id | id = NameBinding::getStaticBindingTarget(expr) | |
There was a problem hiding this comment.
getStaticBindingTarget only works if expr is an Identifier. We should also handle the MemberAccessExpr case here.
This happens several times in this PR so it's probably a good time to improve the interface with NameBinding to avoid this footgun. The idea with current interface is that it's purely a relation between Identifiers, ensuring there is never any ambiguity around which AST node you're supposed to give it. We used to get compilation errors if you compared it with Expr, but after loosing the AST schema that's not the case anymore, and clearly it's not as clear as I thought it was.
I'd suggest we expose these two predicates from NameBinding:
getStaticBindingTargetFromIdentifier: A renamed version ofgetStaticBindingTargetgetStaticBindingTargetFromRef: callsgetStaticBindingTargetFromIdentifier(getIdentifierFromRef(x))
I don't know much it matters for this PR so it's OK to defer to another PR if you want.
| ) { | ||
| associatedTypeParameter(base, a, _) and | ||
| baseRef = c.getABaseType().getType() and | ||
| base.getNameNode() = getStaticBindingTarget(baseRef) and |
There was a problem hiding this comment.
Another case of getStaticBindingTarget being limited to identifiers
| tm = super.getCallee() | ||
| or | ||
| tm = super.getCallee().(MemberAccessExpr).getBase() |
There was a problem hiding this comment.
Won't this have multiple results when referring to a nested class, like A.B where both A and B are classes?
|
|
||
| int getNumberOfArguments() { result = CallExpr.super.getNumberOfArguments() + 1 } | ||
|
|
||
| Expr getArgument(int i) { |
There was a problem hiding this comment.
Named arguments are not supported? (where the call and callable don't declare them in the same order)
| result = getFunctionExprReturnTypePath(getClosureType(c)) | ||
| } | ||
|
|
||
| predicate stepLanguageSpecific(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) { |
There was a problem hiding this comment.
Does LanguageSpecific here mean "specific to this instantiation of the library", or would you expect this to differ between different languages inside unified?
There was a problem hiding this comment.
It means specific to this instantiation of the library. TypeInferencePlugin.inferType provides the hook for language-specific logic.
| abstract predicate isEnumConstructor( | ||
| ClassLikeDeclaration enum, ConstructorDeclaration case, Identifier i | ||
| ); |
There was a problem hiding this comment.
I'm not sure this belongs in the language plugin? What does "enum constructor" actually mean from type inference's perspective?
- Something that can be destructured?
- The enum is assumed to be a closed sum of its case constructors?
There was a problem hiding this comment.
Yes, it will eventually mean something that can be destructured, but right now it means that the return type is not the type of the immediately enclosing class, but instead the class that represents the enum (i.e., Optional.some(0) has type Optional<Int> not some).
| ); | ||
|
|
||
| /** Holds if `field` is an enum field of `enum`. */ | ||
| abstract predicate isEnumField(ClassLikeDeclaration enum, VariableDeclaration field); |
There was a problem hiding this comment.
Same question as above. What does "enum field" actually mean? Does type inference need to distinguish between enum fields and static fields?
There was a problem hiding this comment.
Same reason as below.
|
|
||
| class TypeInferencePlugin extends Unit { | ||
| /** Gets the boolean type which will be assigned to boolean literals. */ | ||
| abstract Type getBoolType(); |
There was a problem hiding this comment.
Perhaps add a TODO comment here about the fact that argument-less predicates in a Unit type can't actually support multiple language plugins, as there is no way to get e.g. the bool type for a specific language.
I'm OK with not solving the problem right now.
| pragma[nomagic] | ||
| override FunctionDeclaration getFunctionInvoke(Type t) { | ||
| result = t.(FunctionType).getClassLikeDeclaration().getAMember() and | ||
| result.getName() = "invoke" |
There was a problem hiding this comment.
| result.getName() = "invoke" | |
| result.getName() = "callAsFunction" |
It's a bit early to try to support callAsFunction in earnest (it's on the language feature backlog), but AFAICT callAsFunction is the correct name to use here.
There was a problem hiding this comment.
The name is not really that important, what matters is that the synthetic function exists. But I can change it.
| not exists(cls.getEnclosingClass()) and | ||
| result = cls.getName() | ||
| or | ||
| result = getStaticBindingTarget(cls.getExtensionTarget()).getValue() |
They should all work; it's only
Destructuring is not expected to work with the |

This PR adds the first version of type inference and type-inference-based call resolution for Unified/Swift. The PR adds a lot of test cases (some of which are copied over from the current Swift implementation, where the annotations have been checked by the Swift compiler), which make up the bulk of the diff of this PR.
Using the shared type inference library, we support already a lot of aspects, for example sub typing and contextual typing (including contextual typing of closures), but there are still a lot of aspects that remain to be handled (a full list of known shortcomings is in the header of
TypeInference.qll), for example overloading and pattern matching.DCA looks great:
Percentage of calls with call targetincreases from 11.5 % to 24.4 % (and as a result more cleartext-logging results) while there is only very little performance impact.