Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
801 changes: 560 additions & 241 deletions shared/typeinference/codeql/typeinference/internal/TypeInference.qll

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ private module Input1 implements InputSig1<Location> {

class TypeAbstraction = TA::TypeAbstraction;

int getTypeParameterVariance(TypeParameter tp) {
result = any(Plugin::TypeInferencePlugin pl).getTypeParameterVariance(tp)
}

int getTypeParameterId(TypeParameter tp) {
CachedStage::ref() and
tp =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class TypeInferencePlugin extends Unit {
/** Gets the type parameter representing the element type of an array. */
abstract TypeParameter getArrayElementTypeParameter();

/** Gets parameter variance: 1 for covariance, -1 for contravariance, or 0 for invariance. */
int getTypeParameterVariance(TypeParameter tp) { exists(tp) and result = 0 }

/**
* Gets the type of the given function expression.
*
Expand All @@ -36,6 +39,9 @@ class TypeInferencePlugin extends Unit {
bindingset[fe]
abstract Type getFunctionExprType(FunctionExpr fe);

/** Gets an intermediate constructor in the type tree of a function signature. */
Type getFunctionExprIntermediateType(FunctionExpr fe, TypePath path) { none() }

/**
* Gets the type path for the `i`th parameter of the given function type
* when the function type has the given arity.
Expand Down Expand Up @@ -127,6 +133,10 @@ TypePath getFunctionExprReturnTypePath(Type t) {
result = any(TypeInferencePlugin pl).getFunctionExprReturnTypePath(t)
}

Type getFunctionExprIntermediateType(FunctionExpr fe, TypePath path) {
result = any(TypeInferencePlugin pl).getFunctionExprIntermediateType(fe, path)
}

class TupleType extends Type {
int arity;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ private class EnumField extends VariableDeclaration {
private predicate inferTypeAlias = inferType/2;

private class SwiftTypeInferencePlugin extends Plugin::TypeInferencePlugin {
override int getTypeParameterVariance(TypeParameter tp) {
if tp = getFunctionArgsTypeParameter()
then result = -1
else
if
tp = getFunctionReturnTypeParameter()
or
tp = any(TupleType t).getATypeParameter()
or
tp = any(ArrayType t).getATypeParameter()
or
tp = any(OptionalType t).getATypeParameter()
then result = 1
else result = 0
}

override Type getBoolType() { result instanceof BoolType }

override Type getIntType() { result instanceof IntType }
Expand All @@ -140,6 +156,11 @@ private class SwiftTypeInferencePlugin extends Plugin::TypeInferencePlugin {
exists(fe)
}

override Type getFunctionExprIntermediateType(FunctionExpr fe, TypePath path) {
result = this.getTupleType(fe.getNumberOfParameters()) and
path = TypePath::singleton(getFunctionArgsTypeParameter())
}

bindingset[t]
override TypePath getFunctionExprParameterTypePath(Type t, int arity, int i) {
result = functionParameterPath(arity, i) and exists(t)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ private class NonAliasExprTypeMention extends ExprTypeMentionImpl {
result = this.getRootType() and
path.isEmpty()
or
result = Plugin::getFunctionExprIntermediateType(this.(FunctionExpr), path)
or
exists(
ClassLikeDeclaration c, AssociatedTypeDeclaration a, ClassLikeDeclaration base,
AssociatedTypeParameterType baseTp, string name
Expand Down
533 changes: 533 additions & 0 deletions unified/ql/test/library-tests/type-inference/type-inference.expected

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
bounds
| boundDoubleActual | T | 1 | | Int |
| boundDoubleNestedActual | T | 0 | | Int |
| boundInvariantActual | T | 0 | | Int |
| boundLowerActual | T | 1 | | Int |
| boundMixedActual | T | 1 | | String |
| boundMixedActual | T | -1 | | Int |
| boundNestedActual | T | 0 | | Int |
| boundOptionalActual | T | 0 | | Int |
| boundShiftActual | T | 0 | | Int |
| boundUpperActual | T | -1 | | Int |
noBounds
| boundContraReverseActual |
| boundInvariantConversionActual |
| boundInvariantFunctionActual |
| boundWrongDirectionActual |
39 changes: 39 additions & 0 deletions unified/ql/test/library-tests/type-inference/variance-bounds.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import codeql.unified.internal.typeinference.Type
import codeql.unified.internal.typeinference.TypeMention
import codeql.unified.internal.typeinference.TypeInference

private class Actual extends Identifier {
Actual() {
this.getValue().matches("bound%Actual") and
this.getFile().getBaseName() = "variance.swift" and
exists(inferType(this, TypePath::nil()))
}

Type getTypeAt(TypePath path) { result = inferType(this, path) }
}

private module Input implements VarianceMatchingInputSig<Actual, TypeMention> {
predicate relevantConstraint(Actual actual, TypeMention constraint) {
exists(actual) and
constraint =
any(FunctionDeclaration f |
actual.getValue() = f.getName() + "Actual" and f.getFile().getBaseName() = "variance.swift"
).getParameter(0).getType()
}
}

private module Match = VarianceMatching<Actual, TypeMention, Input>;

query predicate bounds(string name, string parameter, int relation, string path, string type) {
exists(Actual actual, TypeMention constraint, TypeParameter tp, TypePath p, Type t |
Match::typeBound(actual, constraint, tp, relation, p, t) and
name = actual.getValue() and
parameter = tp.toString() and
path = TypePath::printTypePathVerbose(p) and
type = t.toString()
)
}

query predicate noBounds(string name) {
exists(Actual actual | name = actual.getValue() and not Match::typeBound(actual, _, _, _, _, _))
}
73 changes: 70 additions & 3 deletions unified/ql/test/library-tests/type-inference/variance.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// variance is not currently supported
// Recursive variance through function inputs, outputs, and generic constructors.

class A<Ta> {}

Expand All @@ -10,10 +10,77 @@ func foo<T1, T2>(f: (B<T1>) -> A<T2>) -> (T1, T2) {

let lam = { (a: A<Int>) -> B<String> in B<String>() } // $ target=A.init

let x = foo(f: lam) // $ target=foo $ MISSING: type=x@Tuple2<T0>:Int type=x@Tuple2<T1>:String
let x = foo(f: lam) // $ target=foo type=x@Tuple2<T0>:Int type=x@Tuple2<T1>:String

func bar<T>() -> B<T> { B() } // $ target=A.init

func baz() {
let x: A<Int> = bar() // $ target=bar $ MISSING: type=bar(...)@B<Tb>:Int
let x: A<Int> = bar() // $ target=bar type=bar(...)@B<Tb>:Int
}

func boundLower<T>(_ value: [T]) -> T { fatalError() }
let boundLowerActual = [1]
let lower = boundLower(boundLowerActual) // $ target=boundLower type=lower:Int

func boundUpper<T>(_ callback: (T) -> Int) -> T { fatalError() }
let boundUpperActual = { (value: Int) -> Int in value } // $ type=value:Int
let upper = boundUpper(boundUpperActual) // $ target=boundUpper type=upper:Int

func boundDouble<T>(_ callback: ((T) -> Int) -> Int) -> T { fatalError() }
let boundDoubleActual = { (callback: (Int) -> Int) -> Int in callback(1) } // $ target=Function.callAsFunction
let double = boundDouble(boundDoubleActual) // $ target=boundDouble type=double:Int

func boundInvariant<T>(_ value: A<T>) -> T { fatalError() }
let boundInvariantActual = A<Int>() // $ target=A.init
let invariant = boundInvariant(boundInvariantActual) // $ target=boundInvariant type=invariant:Int

func boundNested<T>(_ value: [A<T>]) -> T { fatalError() }
let boundNestedActual = [B<Int>()] // $ target=A.init
let nested = boundNested(boundNestedActual) // $ target=boundNested type=nested:Int

// Neither reversing a covariant conversion nor converting through an invariant
// parameter can establish a bound. These calls intentionally have incompatible types.
func boundWrongDirection<T>(_ value: [B<T>]) -> T { fatalError() }
let boundWrongDirectionActual = [A<Int>()] // $ target=A.init
let wrongDirection = boundWrongDirection(boundWrongDirectionActual) // $ target=boundWrongDirection

func boundInvariantConversion<T>(_ value: A<A<T>>) -> T { fatalError() }
let boundInvariantConversionActual = A<B<Int>>() // $ target=A.init
let invariantConversion = boundInvariantConversion(boundInvariantConversionActual) // $ target=boundInvariantConversion

func boundContext<T>(_ first: T, _ values: [A<T>]) {}
boundContext(1, [B()]) // $ target=boundContext target=A.init type=B(...)@B<Tb>:Int

func boundAlternatives<T>(_ first: [T], _ second: [T]) -> T { fatalError() }
let alternatives = boundAlternatives([1], ["x"]) // $ target=boundAlternatives type=alternatives:Int type=alternatives:String

// Keep lower and upper bounds distinct, without solving their inconsistency.
func boundMixed<T>(_ callback: (T) -> T) -> T { fatalError() }
let boundMixedActual = { (value: Int) -> String in "x" }
let mixed = boundMixed(boundMixedActual) // $ target=boundMixed type=mixed:Int type=mixed:String

class Shift<C>: A<[C]> {
override init() { super.init() } // $ target=A.init
}
func boundShift<T>(_ value: [A<[T]>]) -> T { fatalError() }
let boundShiftActual = [Shift<Int>()] // $ target=Shift.init
let shifted = boundShift(boundShiftActual) // $ target=boundShift type=shifted:Int

func shiftedContext<T>(_ first: T, _ values: [A<[T]>]) {}
shiftedContext(1, [Shift()]) // $ target=shiftedContext target=Shift.init type=Shift(...)@Shift<C>:Int

func boundContraReverse<T>(_ callback: (A<T>) -> Int) -> T { fatalError() }
let boundContraReverseActual = { (value: B<Int>) -> Int in 1 }
let contraReverse = boundContraReverse(boundContraReverseActual) // $ target=boundContraReverse

func boundDoubleNested<T>(_ callback: ((A<T>) -> Int) -> Int) -> T { fatalError() }
let boundDoubleNestedActual = { (callback: (B<Int>) -> Int) -> Int in 1 }
let doubleNested = boundDoubleNested(boundDoubleNestedActual) // $ target=boundDoubleNested type=doubleNested:Int

func boundInvariantFunction<T>(_ value: A<(A<T>) -> Int>) -> T { fatalError() }
let boundInvariantFunctionActual = A<(B<Int>) -> Int>() // $ target=A.init
let invariantFunction = boundInvariantFunction(boundInvariantFunctionActual) // $ target=boundInvariantFunction

func boundOptional<T>(_ value: A<T>?) -> T { fatalError() }
let boundOptionalActual: B<Int>? = B<Int>() // $ target=A.init
let optional = boundOptional(boundOptionalActual) // $ target=boundOptional type=optional:Int
Loading