From 8c6c28d61fcab8a30c7fcb174d31cfc7c76f4f61 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Sun, 27 Jul 2025 21:14:08 +0200 Subject: [PATCH 1/4] Rust: Add type inference tests for closures --- .../library-tests/type-inference/closure.rs | 76 +++++++++++++ .../test/library-tests/type-inference/main.rs | 42 +------ .../type-inference/type-inference.expected | 107 ++++++++++++------ 3 files changed, 147 insertions(+), 78 deletions(-) create mode 100644 rust/ql/test/library-tests/type-inference/closure.rs diff --git a/rust/ql/test/library-tests/type-inference/closure.rs b/rust/ql/test/library-tests/type-inference/closure.rs new file mode 100644 index 000000000000..f0e92a5cac6d --- /dev/null +++ b/rust/ql/test/library-tests/type-inference/closure.rs @@ -0,0 +1,76 @@ +/// Tests for type inference for closures and higher-order functions. + +mod simple_closures { + pub fn test() { + // A simple closure without type annotations or invocations. + let my_closure = |a, b| a && b; + + let x: i64 = 1i64; // $ type=x:i64 + let add_one = |n| n + 1i64; // $ MISSING: target=add + let _y = add_one(x); // $ MISSING: type=y:i64 + + // The type of `x` is inferred from the closure's argument type. + let x = Default::default(); // $ MISSING: type=x:i64 target=default + let add_zero = |n: i64| n; + let _y = add_zero(x); // $ MISSING: type=_y:i64 + + let _get_bool = || -> bool { + // The return type annotation on the closure lets us infer the type of `b`. + let b = Default::default(); // $ MISSING: type=b:bool target=default + b + }; + + // The parameter type of `id` is inferred from the argument. + let id = |b| b; // $ MISSING: type=x:bool + let _b = id(true); // $ MISSING: type=_b:bool + + // The return type of `id2` is inferred from the type of the call expression. + let id2 = |b| b; + let arg = Default::default(); // $ MISSING: target=default type=arg:bool + let _b2: bool = id2(arg); // $ MISSING: type=_b:bool + } +} + +mod fn_once_trait { + fn return_type i64>(f: F) { + let _return = f(true); // $ MISSING: type=_return:i64 + } + + fn argument_type i64>(f: F) { + let arg = Default::default(); // $ MISSING: type=arg:bool target=default + f(arg); + } + + fn apply B>(f: F, a: A) -> B { + f(a) + } + + fn apply_two(f: impl FnOnce(i64) -> i64) -> i64 { + f(2) + } + + fn test() { + let f = |x: bool| -> i64 { + if x { + 1 + } else { + 0 + } + }; + let _r = apply(f, true); // $ target=apply MISSING: type=_r:i64 + + let f = |x| x + 1; // $ MISSING: type=x:i64 target=add + let _r2 = apply_two(f); // $ target=apply_two type=_r2:i64 + } +} + +mod dyn_fn_once { + fn apply_boxed B + ?Sized>(f: Box, arg: A) -> B { + f(arg) + } + + fn apply_boxed_dyn(f: Box B>, arg: A) { + let _r1 = apply_boxed(f, arg); // $ target=apply_boxed MISSING: type=_r1:B + let _r2 = apply_boxed(Box::new(|_: i64| true), 3); // $ target=apply_boxed target=new MISSING: type=_r2:bool + } +} diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 649d03113cf8..5e4442dfa654 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -2459,46 +2459,7 @@ pub mod pattern_matching_experimental { } } -mod closures { - struct Row { - data: i64, - } - - impl Row { - fn get(&self) -> i64 { - self.data // $ fieldof=Row - } - } - - struct Table { - rows: Vec, - } - - impl Table { - fn new() -> Self { - Table { rows: Vec::new() } // $ target=new - } - - fn count_with(&self, property: impl Fn(Row) -> bool) -> i64 { - 0 // (not implemented) - } - } - - pub fn f() { - Some(1).map(|x| { - let x = x; // $ MISSING: type=x:i32 - println!("{x}"); - }); // $ target=map - - let table = Table::new(); // $ target=new type=table:Table - let result = table.count_with(|row| // $ type=result:i64 - { - let v = row.get(); // $ MISSING: target=get type=v:i64 - v > 0 // $ MISSING: target=gt - }); // $ target=count_with - } -} - +mod closure; mod dereference; mod dyn_type; @@ -2532,6 +2493,5 @@ fn main() { dereference::test(); // $ target=test pattern_matching::test_all_patterns(); // $ target=test_all_patterns pattern_matching_experimental::box_patterns(); // $ target=box_patterns - closures::f(); // $ target=f dyn_type::test(); // $ target=test } diff --git a/rust/ql/test/library-tests/type-inference/type-inference.expected b/rust/ql/test/library-tests/type-inference/type-inference.expected index 010e16c527ad..eec82c89ec15 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -1,4 +1,69 @@ inferType +| closure.rs:6:27:6:27 | a | | {EXTERNAL LOCATION} | bool | +| closure.rs:6:30:6:30 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:6:33:6:33 | a | | {EXTERNAL LOCATION} | bool | +| closure.rs:6:33:6:38 | ... && ... | | {EXTERNAL LOCATION} | bool | +| closure.rs:6:38:6:38 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:8:13:8:13 | x | | {EXTERNAL LOCATION} | i64 | +| closure.rs:8:22:8:25 | 1i64 | | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:31:9:34 | 1i64 | | {EXTERNAL LOCATION} | i64 | +| closure.rs:10:26:10:26 | x | | {EXTERNAL LOCATION} | i64 | +| closure.rs:14:25:14:25 | n | | {EXTERNAL LOCATION} | i64 | +| closure.rs:14:33:14:33 | n | | {EXTERNAL LOCATION} | i64 | +| closure.rs:25:21:25:24 | true | | {EXTERNAL LOCATION} | bool | +| closure.rs:30:13:30:15 | _b2 | | {EXTERNAL LOCATION} | bool | +| closure.rs:30:25:30:32 | id2(...) | | {EXTERNAL LOCATION} | bool | +| closure.rs:35:44:35:44 | f | | closure.rs:35:20:35:41 | F | +| closure.rs:36:23:36:23 | f | | closure.rs:35:20:35:41 | F | +| closure.rs:36:25:36:28 | true | | {EXTERNAL LOCATION} | bool | +| closure.rs:39:46:39:46 | f | | closure.rs:39:22:39:43 | F | +| closure.rs:41:9:41:9 | f | | closure.rs:39:22:39:43 | F | +| closure.rs:44:39:44:39 | f | | closure.rs:44:20:44:36 | F | +| closure.rs:44:45:44:45 | a | | closure.rs:44:14:44:14 | A | +| closure.rs:44:56:46:5 | { ... } | | closure.rs:44:17:44:17 | B | +| closure.rs:45:9:45:9 | f | | closure.rs:44:20:44:36 | F | +| closure.rs:45:9:45:12 | f(...) | | closure.rs:44:17:44:17 | B | +| closure.rs:45:11:45:11 | a | | closure.rs:44:14:44:14 | A | +| closure.rs:48:18:48:18 | f | | closure.rs:48:21:48:43 | ImplTraitTypeRepr | +| closure.rs:48:53:50:5 | { ... } | | {EXTERNAL LOCATION} | i64 | +| closure.rs:49:9:49:9 | f | | closure.rs:48:21:48:43 | ImplTraitTypeRepr | +| closure.rs:49:9:49:12 | f(...) | | {EXTERNAL LOCATION} | i64 | +| closure.rs:49:11:49:11 | 2 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:53:18:53:18 | x | | {EXTERNAL LOCATION} | bool | +| closure.rs:53:34:59:9 | { ... } | | {EXTERNAL LOCATION} | i32 | +| closure.rs:54:13:58:13 | if x {...} else {...} | | {EXTERNAL LOCATION} | i32 | +| closure.rs:54:16:54:16 | x | | {EXTERNAL LOCATION} | bool | +| closure.rs:54:18:56:13 | { ... } | | {EXTERNAL LOCATION} | i32 | +| closure.rs:55:17:55:17 | 1 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:56:20:58:13 | { ... } | | {EXTERNAL LOCATION} | i32 | +| closure.rs:57:17:57:17 | 0 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:60:27:60:30 | true | | {EXTERNAL LOCATION} | bool | +| closure.rs:62:25:62:25 | 1 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:63:13:63:15 | _r2 | | {EXTERNAL LOCATION} | i64 | +| closure.rs:63:19:63:30 | apply_two(...) | | {EXTERNAL LOCATION} | i64 | +| closure.rs:68:54:68:54 | f | | {EXTERNAL LOCATION} | Box | +| closure.rs:68:54:68:54 | f | A | {EXTERNAL LOCATION} | Global | +| closure.rs:68:54:68:54 | f | T | closure.rs:68:26:68:51 | F | +| closure.rs:68:65:68:67 | arg | | closure.rs:68:20:68:20 | A | +| closure.rs:68:78:70:5 | { ... } | | closure.rs:68:23:68:23 | B | +| closure.rs:69:9:69:9 | f | | {EXTERNAL LOCATION} | Box | +| closure.rs:69:9:69:9 | f | A | {EXTERNAL LOCATION} | Global | +| closure.rs:69:9:69:9 | f | T | closure.rs:68:26:68:51 | F | +| closure.rs:69:9:69:14 | f(...) | | closure.rs:68:23:68:23 | B | +| closure.rs:69:11:69:13 | arg | | closure.rs:68:20:68:20 | A | +| closure.rs:72:30:72:30 | f | | {EXTERNAL LOCATION} | Box | +| closure.rs:72:30:72:30 | f | A | {EXTERNAL LOCATION} | Global | +| closure.rs:72:30:72:30 | f | T | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:72:58:72:60 | arg | | closure.rs:72:24:72:24 | A | +| closure.rs:73:31:73:31 | f | | {EXTERNAL LOCATION} | Box | +| closure.rs:73:31:73:31 | f | A | {EXTERNAL LOCATION} | Global | +| closure.rs:73:31:73:31 | f | T | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:73:34:73:36 | arg | | closure.rs:72:24:72:24 | A | +| closure.rs:74:31:74:53 | ...::new(...) | | {EXTERNAL LOCATION} | Box | +| closure.rs:74:31:74:53 | ...::new(...) | A | {EXTERNAL LOCATION} | Global | +| closure.rs:74:41:74:41 | _ | | {EXTERNAL LOCATION} | i64 | +| closure.rs:74:49:74:52 | true | | {EXTERNAL LOCATION} | bool | +| closure.rs:74:56:74:56 | 3 | | {EXTERNAL LOCATION} | i32 | | dereference.rs:12:14:12:18 | SelfParam | | file://:0:0:0:0 | & | | dereference.rs:12:14:12:18 | SelfParam | &T | dereference.rs:4:1:6:1 | MyIntPointer | | dereference.rs:12:29:14:5 | { ... } | | file://:0:0:0:0 | & | @@ -4528,43 +4593,11 @@ inferType | main.rs:2456:26:2456:43 | "Nested boxed: {}\\n" | &T | {EXTERNAL LOCATION} | str | | main.rs:2456:26:2456:59 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | | main.rs:2456:26:2456:59 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2468:16:2468:20 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2468:16:2468:20 | SelfParam | &T | main.rs:2463:5:2465:5 | Row | -| main.rs:2468:30:2470:9 | { ... } | | {EXTERNAL LOCATION} | i64 | -| main.rs:2469:13:2469:16 | self | | file://:0:0:0:0 | & | -| main.rs:2469:13:2469:16 | self | &T | main.rs:2463:5:2465:5 | Row | -| main.rs:2469:13:2469:21 | self.data | | {EXTERNAL LOCATION} | i64 | -| main.rs:2478:26:2480:9 | { ... } | | main.rs:2473:5:2475:5 | Table | -| main.rs:2479:13:2479:38 | Table {...} | | main.rs:2473:5:2475:5 | Table | -| main.rs:2479:27:2479:36 | ...::new(...) | | {EXTERNAL LOCATION} | Vec | -| main.rs:2479:27:2479:36 | ...::new(...) | A | {EXTERNAL LOCATION} | Global | -| main.rs:2479:27:2479:36 | ...::new(...) | T | main.rs:2463:5:2465:5 | Row | -| main.rs:2482:23:2482:27 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2482:23:2482:27 | SelfParam | &T | main.rs:2473:5:2475:5 | Table | -| main.rs:2482:30:2482:37 | property | | main.rs:2482:40:2482:59 | ImplTraitTypeRepr | -| main.rs:2482:69:2484:9 | { ... } | | {EXTERNAL LOCATION} | i32 | -| main.rs:2482:69:2484:9 | { ... } | | {EXTERNAL LOCATION} | i64 | -| main.rs:2483:13:2483:13 | 0 | | {EXTERNAL LOCATION} | i32 | -| main.rs:2483:13:2483:13 | 0 | | {EXTERNAL LOCATION} | i64 | -| main.rs:2488:9:2488:15 | Some(...) | | {EXTERNAL LOCATION} | Option | -| main.rs:2488:9:2488:15 | Some(...) | T | {EXTERNAL LOCATION} | i32 | -| main.rs:2488:9:2491:10 | ... .map(...) | | {EXTERNAL LOCATION} | Option | -| main.rs:2488:14:2488:14 | 1 | | {EXTERNAL LOCATION} | i32 | -| main.rs:2490:22:2490:26 | "{x}\\n" | | file://:0:0:0:0 | & | -| main.rs:2490:22:2490:26 | "{x}\\n" | &T | {EXTERNAL LOCATION} | str | -| main.rs:2490:22:2490:26 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2490:22:2490:26 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2493:13:2493:17 | table | | main.rs:2473:5:2475:5 | Table | -| main.rs:2493:21:2493:32 | ...::new(...) | | main.rs:2473:5:2475:5 | Table | -| main.rs:2494:13:2494:18 | result | | {EXTERNAL LOCATION} | i64 | -| main.rs:2494:22:2494:26 | table | | main.rs:2473:5:2475:5 | Table | -| main.rs:2494:22:2498:14 | table.count_with(...) | | {EXTERNAL LOCATION} | i64 | -| main.rs:2497:21:2497:21 | 0 | | {EXTERNAL LOCATION} | i32 | -| main.rs:2507:5:2507:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2508:5:2508:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2508:20:2508:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2508:41:2508:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2524:5:2524:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | +| main.rs:2468:5:2468:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2469:5:2469:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2469:20:2469:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2469:41:2469:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2485:5:2485:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | | pattern_matching.rs:13:26:133:1 | { ... } | | {EXTERNAL LOCATION} | Option | | pattern_matching.rs:13:26:133:1 | { ... } | T | file://:0:0:0:0 | () | | pattern_matching.rs:14:9:14:13 | value | | {EXTERNAL LOCATION} | Option | From 2c758a98424a328131711f3b9fb932b521bd8f91 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Sun, 27 Jul 2025 21:28:10 +0200 Subject: [PATCH 2/4] Rust: Add type inference for closures and calls to first-class functions --- .../codeql/rust/frameworks/stdlib/Stdlib.qll | 20 +++ .../codeql/rust/internal/TypeInference.qll | 129 +++++++++++++++ .../lib/codeql/rust/internal/TypeMention.qll | 24 +++ .../library-tests/type-inference/closure.rs | 28 ++-- .../type-inference/type-inference.expected | 154 ++++++++++++++++++ 5 files changed, 341 insertions(+), 14 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll b/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll index 05c6103b792a..820c6330c25e 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll @@ -140,6 +140,26 @@ class FutureTrait extends Trait { } } +/** + * The [`FnOnce` trait][1]. + * + * [1]: https://doc.rust-lang.org/std/ops/trait.FnOnce.html + */ +class FnOnceTrait extends Trait { + pragma[nomagic] + FnOnceTrait() { this.getCanonicalPath() = "core::ops::function::FnOnce" } + + /** Gets the type parameter of this trait. */ + TypeParam getTypeParam() { result = this.getGenericParamList().getGenericParam(0) } + + /** Gets the `Output` associated type. */ + pragma[nomagic] + TypeAlias getOutputType() { + result = this.getAssocItemList().getAnAssocItem() and + result.getName().getText() = "Output" + } +} + /** * The [`Iterator` trait][1]. * diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index a7e7823869a5..461045ec5442 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -383,6 +383,17 @@ private predicate typeEquality(AstNode n1, TypePath prefix1, AstNode n2, TypePat prefix2.isEmpty() and s = getRangeType(n1) ) + or + exists(ClosureExpr ce, int index | + n1 = ce and + n2 = ce.getParam(index).getPat() and + prefix1 = closureParameterPath(ce.getNumberOfParams(), index) and + prefix2.isEmpty() + ) + or + n1.(ClosureExpr).getBody() = n2 and + prefix1 = closureReturnPath() and + prefix2.isEmpty() } pragma[nomagic] @@ -1435,6 +1446,120 @@ private Type inferForLoopExprType(AstNode n, TypePath path) { ) } +/** + * An invoked expression, the target of a call that is either a local variable + * or a non-path expression. This means that the expression denotes a + * first-class function. + */ +final private class InvokedClosureExpr extends Expr { + private CallExpr call; + + InvokedClosureExpr() { + call.getFunction() = this and + (not this instanceof PathExpr or this = any(Variable v).getAnAccess()) + } + + Type getTypeAt(TypePath path) { result = inferType(this, path) } + + CallExpr getCall() { result = call } +} + +private module InvokedClosureSatisfiesConstraintInput implements + SatisfiesConstraintInputSig +{ + predicate relevantConstraint(InvokedClosureExpr term, Type constraint) { + exists(term) and + constraint.(TraitType).getTrait() instanceof FnOnceTrait + } +} + +/** Gets the type of `ce` when viewed as an implementation of `FnOnce`. */ +private Type invokedClosureFnTypeAt(InvokedClosureExpr ce, TypePath path) { + SatisfiesConstraint::satisfiesConstraintType(ce, + _, path, result) +} + +/** Gets the path to a closure's return type. */ +private TypePath closureReturnPath() { + result = TypePath::singleton(TDynTraitTypeParameter(any(FnOnceTrait t).getOutputType())) +} + +/** Gets the path to a closure with arity `arity`s `index`th parameter type. */ +private TypePath closureParameterPath(int arity, int index) { + result = + TypePath::cons(TDynTraitTypeParameter(any(FnOnceTrait t).getTypeParam()), + TypePath::singleton(TTupleTypeParameter(arity, index))) +} + +/** Gets the path to the return type of the `FnOnce` trait. */ +private TypePath fnReturnPath() { + result = TypePath::singleton(TAssociatedTypeTypeParameter(any(FnOnceTrait t).getOutputType())) +} + +/** + * Gets the path to the parameter type of the `FnOnce` trait with arity `arity` + * and index `index`. + */ +private TypePath fnParameterPath(int arity, int index) { + result = + TypePath::cons(TTypeParamTypeParameter(any(FnOnceTrait t).getTypeParam()), + TypePath::singleton(TTupleTypeParameter(arity, index))) +} + +pragma[nomagic] +private Type inferDynamicCallExprType(Expr n, TypePath path) { + exists(InvokedClosureExpr ce | + // Propagate the function's return type to the call expression + exists(TypePath path0 | result = invokedClosureFnTypeAt(ce, path0) | + n = ce.getCall() and + path = path0.stripPrefix(fnReturnPath()) + or + // Propagate the function's parameter type to the arguments + exists(int index | + n = ce.getCall().getArgList().getArg(index) and + path = path0.stripPrefix(fnParameterPath(ce.getCall().getNumberOfArgs(), index)) + ) + ) + or + // _If_ the invoked expression has the type of a closure, then we propagate + // the surrounding types into the closure. + exists(int arity, TypePath path0 | + ce.getTypeAt(TypePath::nil()).(DynTraitType).getTrait() instanceof FnOnceTrait + | + // Propagate the type of arguments to the parameter types of closure + exists(int index | + n = ce and + arity = ce.getCall().getNumberOfArgs() and + result = inferType(ce.getCall().getArg(index), path0) and + path = closureParameterPath(arity, index).append(path0) + ) + or + // Propagate the type of the call expression to the return type of the closure + n = ce and + arity = ce.getCall().getNumberOfArgs() and + result = inferType(ce.getCall(), path0) and + path = closureReturnPath().append(path0) + ) + ) +} + +pragma[nomagic] +private Type inferClosureExprType(AstNode n, TypePath path) { + exists(ClosureExpr ce | + n = ce and + path.isEmpty() and + result = TDynTraitType(any(FnOnceTrait t)) + or + n = ce and + path = TypePath::singleton(TDynTraitTypeParameter(any(FnOnceTrait t).getTypeParam())) and + result = TTuple(ce.getNumberOfParams()) + or + // Propagate return type annotation to body + n = ce.getBody() and + result = ce.getRetType().getTypeRepr().(TypeMention).resolveTypeAt(path) + ) +} + pragma[nomagic] private Type inferCastExprType(CastExpr ce, TypePath path) { result = ce.getTypeRepr().(TypeMention).resolveTypeAt(path) @@ -2062,6 +2187,10 @@ private module Cached { or result = inferForLoopExprType(n, path) or + result = inferDynamicCallExprType(n, path) + or + result = inferClosureExprType(n, path) + or result = inferCastExprType(n, path) or result = inferStructPatType(n, path) diff --git a/rust/ql/lib/codeql/rust/internal/TypeMention.qll b/rust/ql/lib/codeql/rust/internal/TypeMention.qll index f337304d708b..6a7413c6d8ac 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeMention.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeMention.qll @@ -1,6 +1,7 @@ /** Provides classes for representing type mentions, used in type inference. */ private import rust +private import codeql.rust.frameworks.stdlib.Stdlib private import Type private import PathResolution private import TypeInference @@ -26,6 +27,18 @@ class TupleTypeReprMention extends TypeMention instanceof TupleTypeRepr { } } +class ParenthesizedArgListMention extends TypeMention instanceof ParenthesizedArgList { + override Type resolveTypeAt(TypePath path) { + path.isEmpty() and + result = TTuple(super.getNumberOfTypeArgs()) + or + exists(TypePath suffix, int index | + result = super.getTypeArg(index).getTypeRepr().(TypeMention).resolveTypeAt(suffix) and + path = TypePath::cons(TTupleTypeParameter(super.getNumberOfTypeArgs(), index), suffix) + ) + } +} + class ArrayTypeReprMention extends TypeMention instanceof ArrayTypeRepr { override Type resolveTypeAt(TypePath path) { path.isEmpty() and @@ -215,6 +228,17 @@ class NonAliasPathTypeMention extends PathTypeMention { .(TraitItemNode) .getAssocItem(pragma[only_bind_into](name))) ) + or + // Handle the special syntactic sugar for function traits. For now we only + // support `FnOnce` as we can't support the "inherited" associated types of + // `Fn` and `FnMut` yet. + exists(FnOnceTrait t | t = resolved | + tp = TTypeParamTypeParameter(t.getTypeParam()) and + result = this.getSegment().getParenthesizedArgList() + or + tp = TAssociatedTypeTypeParameter(t.getOutputType()) and + result = this.getSegment().getRetType().getTypeRepr() + ) } Type resolveRootType() { diff --git a/rust/ql/test/library-tests/type-inference/closure.rs b/rust/ql/test/library-tests/type-inference/closure.rs index f0e92a5cac6d..1b11335947c1 100644 --- a/rust/ql/test/library-tests/type-inference/closure.rs +++ b/rust/ql/test/library-tests/type-inference/closure.rs @@ -6,38 +6,38 @@ mod simple_closures { let my_closure = |a, b| a && b; let x: i64 = 1i64; // $ type=x:i64 - let add_one = |n| n + 1i64; // $ MISSING: target=add - let _y = add_one(x); // $ MISSING: type=y:i64 + let add_one = |n| n + 1i64; // $ target=add + let _y = add_one(x); // $ type=_y:i64 // The type of `x` is inferred from the closure's argument type. - let x = Default::default(); // $ MISSING: type=x:i64 target=default + let x = Default::default(); // $ type=x:i64 target=default let add_zero = |n: i64| n; - let _y = add_zero(x); // $ MISSING: type=_y:i64 + let _y = add_zero(x); // $ type=_y:i64 let _get_bool = || -> bool { // The return type annotation on the closure lets us infer the type of `b`. - let b = Default::default(); // $ MISSING: type=b:bool target=default + let b = Default::default(); // $ type=b:bool target=default b }; // The parameter type of `id` is inferred from the argument. - let id = |b| b; // $ MISSING: type=x:bool - let _b = id(true); // $ MISSING: type=_b:bool + let id = |b| b; // $ type=b:bool + let _b = id(true); // $ type=_b:bool // The return type of `id2` is inferred from the type of the call expression. let id2 = |b| b; - let arg = Default::default(); // $ MISSING: target=default type=arg:bool - let _b2: bool = id2(arg); // $ MISSING: type=_b:bool + let arg = Default::default(); // $ target=default type=arg:bool + let _b2: bool = id2(arg); // $ type=_b2:bool } } mod fn_once_trait { fn return_type i64>(f: F) { - let _return = f(true); // $ MISSING: type=_return:i64 + let _return = f(true); // $ type=_return:i64 } fn argument_type i64>(f: F) { - let arg = Default::default(); // $ MISSING: type=arg:bool target=default + let arg = Default::default(); // $ target=default type=arg:bool f(arg); } @@ -57,7 +57,7 @@ mod fn_once_trait { 0 } }; - let _r = apply(f, true); // $ target=apply MISSING: type=_r:i64 + let _r = apply(f, true); // $ target=apply type=_r:i64 let f = |x| x + 1; // $ MISSING: type=x:i64 target=add let _r2 = apply_two(f); // $ target=apply_two type=_r2:i64 @@ -70,7 +70,7 @@ mod dyn_fn_once { } fn apply_boxed_dyn(f: Box B>, arg: A) { - let _r1 = apply_boxed(f, arg); // $ target=apply_boxed MISSING: type=_r1:B - let _r2 = apply_boxed(Box::new(|_: i64| true), 3); // $ target=apply_boxed target=new MISSING: type=_r2:bool + let _r1 = apply_boxed(f, arg); // $ target=apply_boxed type=_r1:B + let _r2 = apply_boxed(Box::new(|_: i64| true), 3); // $ target=apply_boxed target=new type=_r2:bool } } diff --git a/rust/ql/test/library-tests/type-inference/type-inference.expected b/rust/ql/test/library-tests/type-inference/type-inference.expected index eec82c89ec15..faa806ed163c 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -1,4 +1,14 @@ inferType +| closure.rs:6:13:6:22 | my_closure | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:6:13:6:22 | my_closure | dyn(Args) | file://:0:0:0:0 | (T_2) | +| closure.rs:6:13:6:22 | my_closure | dyn(Args).0(2) | {EXTERNAL LOCATION} | bool | +| closure.rs:6:13:6:22 | my_closure | dyn(Args).1(2) | {EXTERNAL LOCATION} | bool | +| closure.rs:6:13:6:22 | my_closure | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:6:26:6:38 | \|...\| ... | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:6:26:6:38 | \|...\| ... | dyn(Args) | file://:0:0:0:0 | (T_2) | +| closure.rs:6:26:6:38 | \|...\| ... | dyn(Args).0(2) | {EXTERNAL LOCATION} | bool | +| closure.rs:6:26:6:38 | \|...\| ... | dyn(Args).1(2) | {EXTERNAL LOCATION} | bool | +| closure.rs:6:26:6:38 | \|...\| ... | dyn(Output) | {EXTERNAL LOCATION} | bool | | closure.rs:6:27:6:27 | a | | {EXTERNAL LOCATION} | bool | | closure.rs:6:30:6:30 | b | | {EXTERNAL LOCATION} | bool | | closure.rs:6:33:6:33 | a | | {EXTERNAL LOCATION} | bool | @@ -6,18 +16,101 @@ inferType | closure.rs:6:38:6:38 | b | | {EXTERNAL LOCATION} | bool | | closure.rs:8:13:8:13 | x | | {EXTERNAL LOCATION} | i64 | | closure.rs:8:22:8:25 | 1i64 | | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:13:9:19 | add_one | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:9:13:9:19 | add_one | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:9:13:9:19 | add_one | dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:13:9:19 | add_one | dyn(Output) | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:23:9:34 | \|...\| ... | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:9:23:9:34 | \|...\| ... | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:9:23:9:34 | \|...\| ... | dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:23:9:34 | \|...\| ... | dyn(Output) | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:24:9:24 | n | | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:27:9:27 | n | | {EXTERNAL LOCATION} | i64 | +| closure.rs:9:27:9:34 | ... + ... | | {EXTERNAL LOCATION} | i64 | | closure.rs:9:31:9:34 | 1i64 | | {EXTERNAL LOCATION} | i64 | +| closure.rs:10:13:10:14 | _y | | {EXTERNAL LOCATION} | i64 | +| closure.rs:10:18:10:24 | add_one | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:10:18:10:24 | add_one | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:10:18:10:24 | add_one | dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:10:18:10:24 | add_one | dyn(Output) | {EXTERNAL LOCATION} | i64 | +| closure.rs:10:18:10:27 | add_one(...) | | {EXTERNAL LOCATION} | i64 | | closure.rs:10:26:10:26 | x | | {EXTERNAL LOCATION} | i64 | +| closure.rs:13:13:13:13 | x | | {EXTERNAL LOCATION} | i64 | +| closure.rs:13:17:13:34 | ...::default(...) | | {EXTERNAL LOCATION} | i64 | +| closure.rs:14:13:14:20 | add_zero | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:14:13:14:20 | add_zero | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:14:13:14:20 | add_zero | dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:14:13:14:20 | add_zero | dyn(Output) | {EXTERNAL LOCATION} | i64 | +| closure.rs:14:24:14:33 | \|...\| n | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:14:24:14:33 | \|...\| n | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:14:24:14:33 | \|...\| n | dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:14:24:14:33 | \|...\| n | dyn(Output) | {EXTERNAL LOCATION} | i64 | | closure.rs:14:25:14:25 | n | | {EXTERNAL LOCATION} | i64 | | closure.rs:14:33:14:33 | n | | {EXTERNAL LOCATION} | i64 | +| closure.rs:15:13:15:14 | _y | | {EXTERNAL LOCATION} | i64 | +| closure.rs:15:18:15:25 | add_zero | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:15:18:15:25 | add_zero | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:15:18:15:25 | add_zero | dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:15:18:15:25 | add_zero | dyn(Output) | {EXTERNAL LOCATION} | i64 | +| closure.rs:15:18:15:28 | add_zero(...) | | {EXTERNAL LOCATION} | i64 | +| closure.rs:15:27:15:27 | x | | {EXTERNAL LOCATION} | i64 | +| closure.rs:17:13:17:21 | _get_bool | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:17:13:17:21 | _get_bool | dyn(Args) | file://:0:0:0:0 | () | +| closure.rs:17:13:17:21 | _get_bool | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:17:25:21:9 | \|...\| ... | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:17:25:21:9 | \|...\| ... | dyn(Args) | file://:0:0:0:0 | () | +| closure.rs:17:25:21:9 | \|...\| ... | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:17:36:21:9 | { ... } | | {EXTERNAL LOCATION} | bool | +| closure.rs:19:17:19:17 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:19:21:19:38 | ...::default(...) | | {EXTERNAL LOCATION} | bool | +| closure.rs:20:13:20:13 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:24:13:24:14 | id | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:24:13:24:14 | id | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:24:13:24:14 | id | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:24:13:24:14 | id | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:24:18:24:22 | \|...\| b | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:24:18:24:22 | \|...\| b | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:24:18:24:22 | \|...\| b | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:24:18:24:22 | \|...\| b | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:24:19:24:19 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:24:22:24:22 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:25:13:25:14 | _b | | {EXTERNAL LOCATION} | bool | +| closure.rs:25:18:25:19 | id | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:25:18:25:19 | id | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:25:18:25:19 | id | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:25:18:25:19 | id | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:25:18:25:25 | id(...) | | {EXTERNAL LOCATION} | bool | | closure.rs:25:21:25:24 | true | | {EXTERNAL LOCATION} | bool | +| closure.rs:28:13:28:15 | id2 | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:28:13:28:15 | id2 | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:28:13:28:15 | id2 | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:28:13:28:15 | id2 | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:28:19:28:23 | \|...\| b | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:28:19:28:23 | \|...\| b | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:28:19:28:23 | \|...\| b | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:28:19:28:23 | \|...\| b | dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:28:20:28:20 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:28:23:28:23 | b | | {EXTERNAL LOCATION} | bool | +| closure.rs:29:13:29:15 | arg | | {EXTERNAL LOCATION} | bool | +| closure.rs:29:19:29:36 | ...::default(...) | | {EXTERNAL LOCATION} | bool | | closure.rs:30:13:30:15 | _b2 | | {EXTERNAL LOCATION} | bool | +| closure.rs:30:25:30:27 | id2 | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:30:25:30:27 | id2 | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:30:25:30:27 | id2 | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:30:25:30:27 | id2 | dyn(Output) | {EXTERNAL LOCATION} | bool | | closure.rs:30:25:30:32 | id2(...) | | {EXTERNAL LOCATION} | bool | +| closure.rs:30:29:30:31 | arg | | {EXTERNAL LOCATION} | bool | | closure.rs:35:44:35:44 | f | | closure.rs:35:20:35:41 | F | +| closure.rs:36:13:36:19 | _return | | {EXTERNAL LOCATION} | i64 | | closure.rs:36:23:36:23 | f | | closure.rs:35:20:35:41 | F | +| closure.rs:36:23:36:29 | f(...) | | {EXTERNAL LOCATION} | i64 | | closure.rs:36:25:36:28 | true | | {EXTERNAL LOCATION} | bool | | closure.rs:39:46:39:46 | f | | closure.rs:39:22:39:43 | F | +| closure.rs:40:13:40:15 | arg | | {EXTERNAL LOCATION} | bool | +| closure.rs:40:19:40:36 | ...::default(...) | | {EXTERNAL LOCATION} | bool | | closure.rs:41:9:41:9 | f | | closure.rs:39:22:39:43 | F | +| closure.rs:41:9:41:14 | f(...) | | {EXTERNAL LOCATION} | i64 | +| closure.rs:41:11:41:13 | arg | | {EXTERNAL LOCATION} | bool | | closure.rs:44:39:44:39 | f | | closure.rs:44:20:44:36 | F | | closure.rs:44:45:44:45 | a | | closure.rs:44:14:44:14 | A | | closure.rs:44:56:46:5 | { ... } | | closure.rs:44:17:44:17 | B | @@ -29,18 +122,50 @@ inferType | closure.rs:49:9:49:9 | f | | closure.rs:48:21:48:43 | ImplTraitTypeRepr | | closure.rs:49:9:49:12 | f(...) | | {EXTERNAL LOCATION} | i64 | | closure.rs:49:11:49:11 | 2 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:49:11:49:11 | 2 | | {EXTERNAL LOCATION} | i64 | +| closure.rs:53:13:53:13 | f | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:53:13:53:13 | f | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:53:13:53:13 | f | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:53:13:53:13 | f | dyn(Output) | {EXTERNAL LOCATION} | i32 | +| closure.rs:53:13:53:13 | f | dyn(Output) | {EXTERNAL LOCATION} | i64 | +| closure.rs:53:17:59:9 | \|...\| ... | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:53:17:59:9 | \|...\| ... | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:53:17:59:9 | \|...\| ... | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:53:17:59:9 | \|...\| ... | dyn(Output) | {EXTERNAL LOCATION} | i32 | +| closure.rs:53:17:59:9 | \|...\| ... | dyn(Output) | {EXTERNAL LOCATION} | i64 | | closure.rs:53:18:53:18 | x | | {EXTERNAL LOCATION} | bool | | closure.rs:53:34:59:9 | { ... } | | {EXTERNAL LOCATION} | i32 | +| closure.rs:53:34:59:9 | { ... } | | {EXTERNAL LOCATION} | i64 | | closure.rs:54:13:58:13 | if x {...} else {...} | | {EXTERNAL LOCATION} | i32 | +| closure.rs:54:13:58:13 | if x {...} else {...} | | {EXTERNAL LOCATION} | i64 | | closure.rs:54:16:54:16 | x | | {EXTERNAL LOCATION} | bool | | closure.rs:54:18:56:13 | { ... } | | {EXTERNAL LOCATION} | i32 | +| closure.rs:54:18:56:13 | { ... } | | {EXTERNAL LOCATION} | i64 | | closure.rs:55:17:55:17 | 1 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:55:17:55:17 | 1 | | {EXTERNAL LOCATION} | i64 | | closure.rs:56:20:58:13 | { ... } | | {EXTERNAL LOCATION} | i32 | +| closure.rs:56:20:58:13 | { ... } | | {EXTERNAL LOCATION} | i64 | | closure.rs:57:17:57:17 | 0 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:57:17:57:17 | 0 | | {EXTERNAL LOCATION} | i64 | +| closure.rs:60:13:60:14 | _r | | {EXTERNAL LOCATION} | i32 | +| closure.rs:60:13:60:14 | _r | | {EXTERNAL LOCATION} | i64 | +| closure.rs:60:18:60:31 | apply(...) | | {EXTERNAL LOCATION} | i32 | +| closure.rs:60:18:60:31 | apply(...) | | {EXTERNAL LOCATION} | i64 | +| closure.rs:60:24:60:24 | f | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:60:24:60:24 | f | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:60:24:60:24 | f | dyn(Args).0(1) | {EXTERNAL LOCATION} | bool | +| closure.rs:60:24:60:24 | f | dyn(Output) | {EXTERNAL LOCATION} | i32 | +| closure.rs:60:24:60:24 | f | dyn(Output) | {EXTERNAL LOCATION} | i64 | | closure.rs:60:27:60:30 | true | | {EXTERNAL LOCATION} | bool | +| closure.rs:62:13:62:13 | f | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:62:13:62:13 | f | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:62:17:62:25 | \|...\| ... | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:62:17:62:25 | \|...\| ... | dyn(Args) | file://:0:0:0:0 | (T_1) | | closure.rs:62:25:62:25 | 1 | | {EXTERNAL LOCATION} | i32 | | closure.rs:63:13:63:15 | _r2 | | {EXTERNAL LOCATION} | i64 | | closure.rs:63:19:63:30 | apply_two(...) | | {EXTERNAL LOCATION} | i64 | +| closure.rs:63:29:63:29 | f | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:63:29:63:29 | f | dyn(Args) | file://:0:0:0:0 | (T_1) | | closure.rs:68:54:68:54 | f | | {EXTERNAL LOCATION} | Box | | closure.rs:68:54:68:54 | f | A | {EXTERNAL LOCATION} | Global | | closure.rs:68:54:68:54 | f | T | closure.rs:68:26:68:51 | F | @@ -54,16 +179,35 @@ inferType | closure.rs:72:30:72:30 | f | | {EXTERNAL LOCATION} | Box | | closure.rs:72:30:72:30 | f | A | {EXTERNAL LOCATION} | Global | | closure.rs:72:30:72:30 | f | T | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:72:30:72:30 | f | T.dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:72:30:72:30 | f | T.dyn(Args).0(1) | closure.rs:72:24:72:24 | A | +| closure.rs:72:30:72:30 | f | T.dyn(Output) | closure.rs:72:27:72:27 | B | | closure.rs:72:58:72:60 | arg | | closure.rs:72:24:72:24 | A | +| closure.rs:73:13:73:15 | _r1 | | closure.rs:72:27:72:27 | B | +| closure.rs:73:19:73:37 | apply_boxed(...) | | closure.rs:72:27:72:27 | B | | closure.rs:73:31:73:31 | f | | {EXTERNAL LOCATION} | Box | | closure.rs:73:31:73:31 | f | A | {EXTERNAL LOCATION} | Global | | closure.rs:73:31:73:31 | f | T | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:73:31:73:31 | f | T.dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:73:31:73:31 | f | T.dyn(Args).0(1) | closure.rs:72:24:72:24 | A | +| closure.rs:73:31:73:31 | f | T.dyn(Output) | closure.rs:72:27:72:27 | B | | closure.rs:73:34:73:36 | arg | | closure.rs:72:24:72:24 | A | +| closure.rs:74:13:74:15 | _r2 | | {EXTERNAL LOCATION} | bool | +| closure.rs:74:19:74:57 | apply_boxed(...) | | {EXTERNAL LOCATION} | bool | | closure.rs:74:31:74:53 | ...::new(...) | | {EXTERNAL LOCATION} | Box | | closure.rs:74:31:74:53 | ...::new(...) | A | {EXTERNAL LOCATION} | Global | +| closure.rs:74:31:74:53 | ...::new(...) | T | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:74:31:74:53 | ...::new(...) | T.dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:74:31:74:53 | ...::new(...) | T.dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:74:31:74:53 | ...::new(...) | T.dyn(Output) | {EXTERNAL LOCATION} | bool | +| closure.rs:74:40:74:52 | \|...\| true | | {EXTERNAL LOCATION} | dyn FnOnce | +| closure.rs:74:40:74:52 | \|...\| true | dyn(Args) | file://:0:0:0:0 | (T_1) | +| closure.rs:74:40:74:52 | \|...\| true | dyn(Args).0(1) | {EXTERNAL LOCATION} | i64 | +| closure.rs:74:40:74:52 | \|...\| true | dyn(Output) | {EXTERNAL LOCATION} | bool | | closure.rs:74:41:74:41 | _ | | {EXTERNAL LOCATION} | i64 | | closure.rs:74:49:74:52 | true | | {EXTERNAL LOCATION} | bool | | closure.rs:74:56:74:56 | 3 | | {EXTERNAL LOCATION} | i32 | +| closure.rs:74:56:74:56 | 3 | | {EXTERNAL LOCATION} | i64 | | dereference.rs:12:14:12:18 | SelfParam | | file://:0:0:0:0 | & | | dereference.rs:12:14:12:18 | SelfParam | &T | dereference.rs:4:1:6:1 | MyIntPointer | | dereference.rs:12:29:14:5 | { ... } | | file://:0:0:0:0 | & | @@ -2609,6 +2753,8 @@ inferType | main.rs:1433:17:1433:18 | TryExpr | | {EXTERNAL LOCATION} | Result | | main.rs:1433:17:1433:18 | TryExpr | T | main.rs:1407:5:1408:14 | S1 | | main.rs:1433:17:1433:29 | ... .map(...) | | {EXTERNAL LOCATION} | Result | +| main.rs:1433:24:1433:28 | \|...\| s | | {EXTERNAL LOCATION} | dyn FnOnce | +| main.rs:1433:24:1433:28 | \|...\| s | dyn(Args) | file://:0:0:0:0 | (T_1) | | main.rs:1434:9:1434:22 | ...::Ok(...) | | {EXTERNAL LOCATION} | Result | | main.rs:1434:9:1434:22 | ...::Ok(...) | E | main.rs:1410:5:1411:14 | S2 | | main.rs:1434:9:1434:22 | ...::Ok(...) | T | main.rs:1407:5:1408:14 | S1 | @@ -2625,9 +2771,15 @@ inferType | main.rs:1440:21:1440:25 | input | T | main.rs:1439:20:1439:27 | T | | main.rs:1440:21:1440:26 | TryExpr | | main.rs:1439:20:1439:27 | T | | main.rs:1441:22:1441:38 | ...::Ok(...) | | {EXTERNAL LOCATION} | Result | +| main.rs:1441:22:1441:38 | ...::Ok(...) | E | main.rs:1407:5:1408:14 | S1 | | main.rs:1441:22:1441:38 | ...::Ok(...) | T | main.rs:1439:20:1439:27 | T | | main.rs:1441:22:1444:10 | ... .and_then(...) | | {EXTERNAL LOCATION} | Result | +| main.rs:1441:22:1444:10 | ... .and_then(...) | E | main.rs:1407:5:1408:14 | S1 | | main.rs:1441:33:1441:37 | value | | main.rs:1439:20:1439:27 | T | +| main.rs:1441:49:1444:9 | \|...\| ... | | {EXTERNAL LOCATION} | dyn FnOnce | +| main.rs:1441:49:1444:9 | \|...\| ... | dyn(Args) | file://:0:0:0:0 | (T_1) | +| main.rs:1441:49:1444:9 | \|...\| ... | dyn(Output) | {EXTERNAL LOCATION} | Result | +| main.rs:1441:49:1444:9 | \|...\| ... | dyn(Output).E | main.rs:1407:5:1408:14 | S1 | | main.rs:1441:53:1444:9 | { ... } | | {EXTERNAL LOCATION} | Result | | main.rs:1441:53:1444:9 | { ... } | E | main.rs:1407:5:1408:14 | S1 | | main.rs:1442:22:1442:27 | "{:?}\\n" | | file://:0:0:0:0 | & | @@ -3874,6 +4026,8 @@ inferType | main.rs:2231:19:2231:19 | 1 | | {EXTERNAL LOCATION} | i32 | | main.rs:2231:22:2231:22 | 2 | | {EXTERNAL LOCATION} | i32 | | main.rs:2231:25:2231:25 | 3 | | {EXTERNAL LOCATION} | i32 | +| main.rs:2231:32:2231:40 | \|...\| ... | | {EXTERNAL LOCATION} | dyn FnOnce | +| main.rs:2231:32:2231:40 | \|...\| ... | dyn(Args) | file://:0:0:0:0 | (T_1) | | main.rs:2231:40:2231:40 | 1 | | {EXTERNAL LOCATION} | i32 | | main.rs:2232:13:2232:13 | i | | {EXTERNAL LOCATION} | Item | | main.rs:2232:13:2232:13 | i | | {EXTERNAL LOCATION} | i32 | From 8e474c946ef6bb46efe07159177fae2090022a23 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Mon, 28 Jul 2025 10:27:33 +0200 Subject: [PATCH 3/4] Rust: Add change note for type inference for closures --- .../src/change-notes/2025-07-28-type-inference-closures.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 rust/ql/src/change-notes/2025-07-28-type-inference-closures.md diff --git a/rust/ql/src/change-notes/2025-07-28-type-inference-closures.md b/rust/ql/src/change-notes/2025-07-28-type-inference-closures.md new file mode 100644 index 000000000000..a5c110631146 --- /dev/null +++ b/rust/ql/src/change-notes/2025-07-28-type-inference-closures.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Type inference now supports closures, calls to closures, and trait bounds + sing the `FnOnce` trait. \ No newline at end of file From 5b152cfdec2316b25687e12059fdd1dce3c761a9 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Tue, 29 Jul 2025 18:38:14 +0200 Subject: [PATCH 4/4] Rust: Fix typo in change note Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com> --- rust/ql/src/change-notes/2025-07-28-type-inference-closures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/ql/src/change-notes/2025-07-28-type-inference-closures.md b/rust/ql/src/change-notes/2025-07-28-type-inference-closures.md index a5c110631146..6aeeb6ae7965 100644 --- a/rust/ql/src/change-notes/2025-07-28-type-inference-closures.md +++ b/rust/ql/src/change-notes/2025-07-28-type-inference-closures.md @@ -2,4 +2,4 @@ category: minorAnalysis --- * Type inference now supports closures, calls to closures, and trait bounds - sing the `FnOnce` trait. \ No newline at end of file + using the `FnOnce` trait. \ No newline at end of file