Merge pull request #19993 from hvitved/rust/type-inference-function-call-expectations

Rust: Add type inference inline expectations for all function calls
This commit is contained in:
Tom Hvitved
2025-07-07 20:40:57 +02:00
committed by GitHub
6 changed files with 2695 additions and 2680 deletions

View File

@@ -15,3 +15,6 @@ import codeql.rust.elements.AsyncBlockExpr
import codeql.rust.elements.Variable
import codeql.rust.elements.NamedFormatArgument
import codeql.rust.elements.PositionalFormatArgument
private import codeql.rust.elements.Call as Call
class Call = Call::Call;

View File

@@ -1,9 +1,9 @@
multipleCallTargets
| dereference.rs:61:15:61:24 | e1.deref() |
| main.rs:2086:13:2086:31 | ...::from(...) |
| main.rs:2087:13:2087:31 | ...::from(...) |
| main.rs:2088:13:2088:31 | ...::from(...) |
| main.rs:2094:13:2094:31 | ...::from(...) |
| main.rs:2095:13:2095:31 | ...::from(...) |
| main.rs:2096:13:2096:31 | ...::from(...) |
| main.rs:2132:21:2132:43 | ...::from(...) |
| main.rs:2097:13:2097:31 | ...::from(...) |
| main.rs:2098:13:2098:31 | ...::from(...) |
| main.rs:2104:13:2104:31 | ...::from(...) |
| main.rs:2105:13:2105:31 | ...::from(...) |
| main.rs:2106:13:2106:31 | ...::from(...) |
| main.rs:2142:21:2142:43 | ...::from(...) |

View File

@@ -71,15 +71,15 @@ fn explicit_ref_dereference() {
fn explicit_box_dereference() {
// Explicit dereference with type parameter
let g1: Box<char> = Box::new('a');
let g1: Box<char> = Box::new('a'); // $ method=new
let _h1 = g1.deref(); // $ method=deref type=_h1:&T.char
// Explicit dereference with type parameter
let g2: Box<char> = Box::new('a');
let g2: Box<char> = Box::new('a'); // $ method=new
let _h2 = *g2; // $ method=deref type=_h2:char
// Call method on explicitly dereferenced value with type parameter
let g3: Box<i64> = Box::new(34i64);
let g3: Box<i64> = Box::new(34i64); // $ method=new
let _h3 = (*g3).is_positive(); // $ method=deref method=is_positive type=_h3:bool
}
@@ -94,9 +94,9 @@ fn implicit_dereference() {
}
pub fn test() {
explicit_monomorphic_dereference();
explicit_polymorphic_dereference();
explicit_ref_dereference();
explicit_box_dereference();
implicit_dereference();
explicit_monomorphic_dereference(); // $ method=explicit_monomorphic_dereference
explicit_polymorphic_dereference(); // $ method=explicit_polymorphic_dereference
explicit_ref_dereference(); // $ method=explicit_ref_dereference
explicit_box_dereference(); // $ method=explicit_box_dereference
implicit_dereference(); // $ method=implicit_dereference
}

View File

@@ -63,8 +63,8 @@ mod field_access {
}
pub fn f() {
simple_field_access();
generic_field_access();
simple_field_access(); // $ method=simple_field_access
generic_field_access(); // $ method=generic_field_access
}
}
@@ -234,7 +234,7 @@ mod method_non_parametric_trait_impl {
{
// MyThing<S3>::m1
fn m1(self) -> TD {
TD::default()
TD::default() // $ method=default
}
}
@@ -362,34 +362,34 @@ mod method_non_parametric_trait_impl {
// Tests for inference of type parameters based on trait implementations.
let x = call_trait_m1(thing_s1); // $ type=x:S1
let x = call_trait_m1(thing_s1); // $ type=x:S1 method=call_trait_m1
println!("{:?}", x);
let y = call_trait_m1(thing_s2); // $ type=y:MyThing type=y:A.S2
let y = call_trait_m1(thing_s2); // $ type=y:MyThing type=y:A.S2 method=call_trait_m1
println!("{:?}", y.a); // $ fieldof=MyThing
// First implementation
let a = MyPair { p1: S1, p2: S1 };
let x = get_fst(a); // $ type=x:S1
let x = get_fst(a); // $ type=x:S1 method=get_fst
println!("{:?}", x);
let y = get_snd(a); // $ type=y:S1
let y = get_snd(a); // $ type=y:S1 method=get_snd
println!("{:?}", y);
// Second implementation
let b = MyPair { p1: S2, p2: S1 };
let x = get_fst(b); // $ type=x:S1
let x = get_fst(b); // $ type=x:S1 method=get_fst
println!("{:?}", x);
let y = get_snd(b); // $ type=y:S2
let y = get_snd(b); // $ type=y:S2 method=get_snd
println!("{:?}", y);
let c = MyPair {
p1: S3,
p2: MyPair { p1: S2, p2: S1 },
};
let x = get_snd_fst(c); // $ type=x:S1
let x = get_snd_fst(c); // $ type=x:S1 method=get_snd_fst
let thing = MyThing { a: S1 };
let i = thing.convert_to(); // $ MISSING: type=i:S1 method=T::convert_to
let j = convert_to(thing); // $ type=j:S1
let j = convert_to(thing); // $ type=j:S1 method=convert_to
}
}
@@ -603,11 +603,13 @@ mod function_trait_bounds {
}
// Type parameter with bound occurs in the root of a parameter type.
fn call_trait_m1<T1, T2: MyTrait<T1>>(x: T2) -> T1 {
x.m1() // $ method=m1 type=x.m1():T1
}
// Type parameter with bound occurs nested within another type.
fn call_trait_thing_m1<T1, T2: MyTrait<T1>>(x: MyThing<T2>) -> T1 {
x.a.m1() // $ fieldof=MyThing method=m1
}
@@ -634,8 +636,8 @@ mod function_trait_bounds {
let x2 = MyThing { a: S1 };
let y2 = MyThing { a: S2 };
println!("{:?}", call_trait_m1(x2));
println!("{:?}", call_trait_m1(y2));
println!("{:?}", call_trait_m1(x2)); // $ method=call_trait_m1
println!("{:?}", call_trait_m1(y2)); // $ method=call_trait_m1
let x3 = MyThing {
a: MyThing { a: S1 },
@@ -644,9 +646,9 @@ mod function_trait_bounds {
a: MyThing { a: S2 },
};
let a = call_trait_thing_m1(x3); // $ type=a:S1
let a = call_trait_thing_m1(x3); // $ type=a:S1 method=call_trait_thing_m1
println!("{:?}", a);
let b = call_trait_thing_m1(y3); // $ type=b:S2
let b = call_trait_thing_m1(y3); // $ type=b:S2 method=call_trait_thing_m1
println!("{:?}", b);
}
}
@@ -748,6 +750,7 @@ mod trait_associated_type {
}
// Function that returns an associated type from a trait bound
fn g<T: MyTrait>(thing: T) -> <T as MyTrait>::AssociatedType {
thing.m1() // $ method=MyTrait::m1
}
@@ -786,7 +789,7 @@ mod trait_associated_type {
// Call to default implementation in `trait` block
println!("{:?}", x3.putTwo(2, 3).unwrap()); // $ method=putTwo method=unwrap
let x4 = g(S); // $ MISSING: type=x4:AT
let x4 = g(S); // $ method=g $ MISSING: type=x4:AT
println!("{:?}", x4);
let x5 = S2;
@@ -929,10 +932,10 @@ mod method_supertraits {
println!("{:?}", y.m3()); // $ method=m3 type=y.m3():S2
let x = MyThing { a: S1 };
let s = call_trait_m1(x); // $ type=s:S1
let s = call_trait_m1(x); // $ type=s:S1 method=call_trait_m1
let x = MyThing2 { a: S2 };
let s = call_trait_m1(x); // $ type=s:MyThing type=s:A.S2
let s = call_trait_m1(x); // $ type=s:MyThing type=s:A.S2 method=call_trait_m1
}
}
@@ -968,19 +971,20 @@ mod function_trait_bounds_2 {
pub fn f() {
let x = S1;
println!("{:?}", id(&x));
println!("{:?}", id(&x)); // $ method=id
let x = S1;
println!("{:?}", id::<S1>(&x));
println!("{:?}", id::<S1>(&x)); // $ method=id
let x = S1;
println!("{:?}", id::<dyn Trait>(&x)); // incorrectly has type `S1` instead of `Trait`
// incorrectly has type `S1` instead of `Trait`
println!("{:?}", id::<dyn Trait>(&x)); // $ method=id
let x = S1;
into::<S1, S2>(x);
into::<S1, S2>(x); // $ method=into
let x = S1;
let y: S2 = into(x);
let y: S2 = into(x); // $ method=into
}
}
@@ -1055,7 +1059,7 @@ mod type_aliases {
let p3: AnotherPair<S3> = PairOption::PairNone(); // $ type=p3:Fst.S2 type=p3:Snd.S3
println!("{:?}", p3);
g(PairOption::PairSnd(PairOption::PairSnd(S3)));
g(PairOption::PairSnd(PairOption::PairSnd(S3))); // $ method=g
let x: S7<S2>; // $ type=x:Result $ type=x:E.S1 $ type=x:T.S4 $ type=x:T.T41.S2 $ type=x:T.T42.S5 $ type=x:T.T42.T5.S2
}
@@ -1101,19 +1105,20 @@ mod option_methods {
struct S;
pub fn f() {
let x1 = MyOption::<S>::new(); // $ type=x1:T.S
let x1 = MyOption::<S>::new(); // $ type=x1:T.S method=new
println!("{:?}", x1);
let mut x2 = MyOption::new();
let mut x2 = MyOption::new(); // $ method=new
x2.set(S); // $ method=MyOption::set
println!("{:?}", x2);
let mut x3 = MyOption::new(); // missing type `S` from `MyOption<S>` (but can resolve `MyTrait<S>`)
// missing type `S` from `MyOption<S>` (but can resolve `MyTrait<S>`)
let mut x3 = MyOption::new(); // $ method=new
x3.call_set(S); // $ method=call_set
println!("{:?}", x3);
let mut x4 = MyOption::new();
MyOption::set(&mut x4, S);
let mut x4 = MyOption::new(); // $ method=new
MyOption::set(&mut x4, S); // $ method=MyOption::set
println!("{:?}", x4);
let x5 = MyOption::MySome(MyOption::<S>::MyNone());
@@ -1204,8 +1209,8 @@ mod method_call_type_conversion {
let x3 = S(S2);
// explicit borrow
println!("{:?}", S::<S2>::m2(&x3));
println!("{:?}", S::<S2>::m3(&x3));
println!("{:?}", S::<S2>::m2(&x3)); // $ method=m2
println!("{:?}", S::<S2>::m3(&x3)); // $ method=m3
let x4 = &S(S2);
// explicit borrow
@@ -1325,14 +1330,14 @@ mod borrowed_typed {
let x = S {};
x.f1(); // $ method=f1
x.f2(); // $ method=f2
S::f3(&x);
S::f3(&x); // $ method=f3
let n = **&&true; // $ type=n:bool method=deref
// In this example the type of `flag` must be inferred at the call to
// `flip` and flow through the borrow in the argument.
let mut flag = Default::default();
MyFlag::flip(&mut flag);
let mut flag = Default::default(); // $ method=default
MyFlag::flip(&mut flag); // $ method=flip
println!("{:?}", flag); // $ type=flag:MyFlag
}
}
@@ -1347,12 +1352,14 @@ mod try_expressions {
struct S2;
// Simple function using ? operator with same error types
fn try_same_error() -> Result<S1, S1> {
let x = Result::Ok(S1)?; // $ type=x:S1
Result::Ok(S1)
}
// Function using ? operator with different error types that need conversion
fn try_convert_error() -> Result<S1, S2> {
let x = Result::Ok(S1);
let y = x?; // $ type=y:S1
@@ -1360,6 +1367,7 @@ mod try_expressions {
}
// Chained ? operations
fn try_chained() -> Result<S1, S2> {
let x = Result::Ok(Result::Ok(S1));
// First ? returns Result<S1, S2>, second ? returns S1
@@ -1368,6 +1376,7 @@ mod try_expressions {
}
// Function that uses ? with closures and complex error cases
fn try_complex<T: Debug>(input: Result<T, S1>) -> Result<T, S1> {
let value = input?;
let mapped = Result::Ok(value).and_then(|v| {
@@ -1377,20 +1386,21 @@ mod try_expressions {
Result::Err(S1)
}
#[rustfmt::skip]
pub fn f() {
if let Result::Ok(result) = try_same_error() {
if let Result::Ok(result) = try_same_error() { // $ method=try_same_error
println!("{:?}", result);
}
if let Result::Ok(result) = try_convert_error() {
if let Result::Ok(result) = try_convert_error() { // $ method=try_convert_error
println!("{:?}", result);
}
if let Result::Ok(result) = try_chained() {
if let Result::Ok(result) = try_chained() { // $ method=try_chained
println!("{:?}", result);
}
if let Result::Ok(result) = try_complex(Result::Ok(S1)) {
if let Result::Ok(result) = try_complex(Result::Ok(S1)) { // $ method=try_complex
println!("{:?}", result);
}
}
@@ -1789,12 +1799,12 @@ mod overloadable_operators {
let vec2_not = !v1; // $ type=vec2_not:Vec2 method=Vec2::not
// Here the type of `default_vec2` must be inferred from the `+` call.
let default_vec2 = Default::default(); // $ type=default_vec2:Vec2
let default_vec2 = Default::default(); // $ type=default_vec2:Vec2 method=default
let vec2_zero_plus = Vec2 { x: 0, y: 0 } + default_vec2; // $ method=Vec2::add
// Here the type of `default_vec2` must be inferred from the `==` call
// and the type of the borrowed second argument is unknown at the call.
let default_vec2 = Default::default(); // $ type=default_vec2:Vec2
let default_vec2 = Default::default(); // $ type=default_vec2:Vec2 method=default
let vec2_zero_plus = Vec2 { x: 0, y: 0 } == default_vec2; // $ method=Vec2::eq
}
}
@@ -1834,9 +1844,9 @@ mod async_ {
}
pub async fn f() {
f1().await.f(); // $ method=S1f
f2().await.f(); // $ method=S1f
f3().await.f(); // $ method=S1f
f1().await.f(); // $ method=S1f method=f1
f2().await.f(); // $ method=S1f method=f2
f3().await.f(); // $ method=S1f method=f3
S2.await.f(); // $ method=S1f
let b = async { S1 };
b.await.f(); // $ method=S1f
@@ -1890,14 +1900,14 @@ mod impl_trait {
}
pub fn f() {
let x = f1();
let x = f1(); // $ method=f1
x.f1(); // $ method=Trait1f1
x.f2(); // $ method=Trait2f2
let a = get_a_my_trait();
let b = uses_my_trait1(a); // $ type=b:S2
let a = get_a_my_trait();
let c = uses_my_trait2(a); // $ type=c:S2
let d = uses_my_trait2(S1); // $ type=d:S2
let a = get_a_my_trait(); // $ method=get_a_my_trait
let b = uses_my_trait1(a); // $ type=b:S2 method=uses_my_trait1
let a = get_a_my_trait(); // $ method=get_a_my_trait
let c = uses_my_trait2(a); // $ type=c:S2 method=uses_my_trait2
let d = uses_my_trait2(S1); // $ type=d:S2 method=uses_my_trait2
}
}
@@ -1920,7 +1930,7 @@ mod indexers {
impl<T> MyVec<T> {
fn new() -> Self {
MyVec { data: Vec::new() }
MyVec { data: Vec::new() } // $ method=new
}
fn push(&mut self, value: T) {
@@ -1948,14 +1958,14 @@ mod indexers {
}
pub fn f() {
let mut vec = MyVec::new(); // $ type=vec:T.S
let mut vec = MyVec::new(); // $ type=vec:T.S method=new
vec.push(S); // $ method=push
vec[0].foo(); // $ method=MyVec::index method=foo
let xs: [S; 1] = [S];
let x = xs[0].foo(); // $ method=foo type=x:S method=index
analyze_slice(&xs);
analyze_slice(&xs); // $ method=analyze_slice
}
}
@@ -2083,21 +2093,21 @@ mod loops {
let strings2 = // $ type=strings2:[T;...].String
[
String::from("foo"),
String::from("bar"),
String::from("baz"),
String::from("foo"), // $ method=from
String::from("bar"), // $ method=from
String::from("baz"), // $ method=from
];
for s in strings2 {} // $ type=s:String
let strings3 = // $ type=strings3:&T.[T;...].String
&[
String::from("foo"),
String::from("bar"),
String::from("baz"),
String::from("foo"), // $ method=from
String::from("bar"), // $ method=from
String::from("baz"), // $ method=from
];
for s in strings3 {} // $ MISSING: type=s:String
let callables = [MyCallable::new(), MyCallable::new(), MyCallable::new()]; // $ MISSING: type=callables:[T;...].MyCallable; 3
let callables = [MyCallable::new(), MyCallable::new(), MyCallable::new()]; // $ method=new $ MISSING: type=callables:[T;...].MyCallable; 3
for c // $ type=c:MyCallable
in callables
{
@@ -2129,13 +2139,13 @@ mod loops {
let vals4b = [1u16, 2, 3].to_vec(); // $ MISSING: type=vals4b:Vec type=vals4b:T.u16
for u in vals4b {} // $ MISSING: type=u:u16
let vals5 = Vec::from([1u32, 2, 3]); // $ type=vals5:Vec MISSING: type=vals5:T.u32
let vals5 = Vec::from([1u32, 2, 3]); // $ type=vals5:Vec method=from MISSING: type=vals5:T.u32
for u in vals5 {} // $ MISSING: type=u:u32
let vals6: Vec<&u64> = [1u64, 2, 3].iter().collect(); // $ type=vals6:Vec type=vals6:T.&T.u64
for u in vals6 {} // $ type=u:&T.u64
let mut vals7 = Vec::new(); // $ type=vals7:Vec MISSING: type=vals7:T.u8
let mut vals7 = Vec::new(); // $ method=new type=vals7:Vec MISSING: type=vals7:T.u8
vals7.push(1u8); // $ method=push
for u in vals7 {} // $ MISSING: type=u:u8
@@ -2146,9 +2156,9 @@ mod loops {
}
}
let mut map1 = std::collections::HashMap::new(); // $ MISSING: type=map1:Hashmap type=map1:K.i32 type=map1:V.Box type1=map1:V.T.&T.str
map1.insert(1, Box::new("one")); // $ method=insert
map1.insert(2, Box::new("two")); // $ method=insert
let mut map1 = std::collections::HashMap::new(); // $ method=new $ MISSING: type=map1:Hashmap type=map1:K.i32 type=map1:V.Box type1=map1:V.T.&T.str
map1.insert(1, Box::new("one")); // $ method=insert method=new
map1.insert(2, Box::new("two")); // $ method=insert method=new
for key in map1.keys() {} // $ method=keys MISSING: type=key:i32
for value in map1.values() {} // $ method=values MISSING: type=value:Box type=value:T.&T.str
for (key, value) in map1.iter() {} // $ method=iter MISSING: type=key:i32 type=value:Box type=value:T.&T.str
@@ -2179,7 +2189,7 @@ mod explicit_type_args {
}
fn default() -> Self {
S1(T::default())
S1(T::default()) // $ method=default
}
fn method(self) -> Self {
@@ -2196,52 +2206,52 @@ mod explicit_type_args {
}
pub fn f() {
let x1: Option<S1<S2>> = S1::assoc_fun(); // $ type=x1:T.T.S2
let x2 = S1::<S2>::assoc_fun(); // $ type=x2:T.T.S2
let x3 = S3::assoc_fun(); // $ type=x3:T.T.S2
let x4 = S1::<S2>::method(S1::default()); // $ method=method type=x4:T.S2
let x5 = S3::method(S1::default()); // $ method=method type=x5:T.S2
let x6 = S4::<S2>(Default::default()); // $ type=x6:T4.S2
let x1: Option<S1<S2>> = S1::assoc_fun(); // $ type=x1:T.T.S2 method=assoc_fun
let x2 = S1::<S2>::assoc_fun(); // $ type=x2:T.T.S2 method=assoc_fun
let x3 = S3::assoc_fun(); // $ type=x3:T.T.S2 method=assoc_fun
let x4 = S1::<S2>::method(S1::default()); // $ method=method method=default type=x4:T.S2
let x5 = S3::method(S1::default()); // $ method=method method=default type=x5:T.S2
let x6 = S4::<S2>(Default::default()); // $ type=x6:T4.S2 method=default
let x7 = S4(S2); // $ type=x7:T4.S2
let x8 = S4(0); // $ type=x8:T4.i32
let x9 = S4(S2::default()); // $ type=x9:T4.S2
let x9 = S4(S2::default()); // $ type=x9:T4.S2 method=default
let x10 = S5::<S2> // $ type=x10:T5.S2
{
field: Default::default(),
field: Default::default(), // $ method=default
};
let x11 = S5 { field: S2 }; // $ type=x11:T5.S2
let x12 = S5 { field: 0 }; // $ type=x12:T5.i32
let x13 = S5 // $ type=x13:T5.S2
{
field: S2::default(),
field: S2::default(), // $ method=default
};
}
}
fn main() {
field_access::f();
method_impl::f();
method_impl::g(method_impl::Foo {}, method_impl::Foo {});
method_non_parametric_impl::f();
method_non_parametric_trait_impl::f();
function_trait_bounds::f();
trait_associated_type::f();
generic_enum::f();
method_supertraits::f();
function_trait_bounds_2::f();
option_methods::f();
method_call_type_conversion::f();
trait_implicit_self_borrow::f();
implicit_self_borrow::f();
borrowed_typed::f();
try_expressions::f();
builtins::f();
operators::f();
async_::f();
impl_trait::f();
indexers::f();
loops::f();
macros::f();
method_determined_by_argument_type::f();
dereference::test();
field_access::f(); // $ method=f
method_impl::f(); // $ method=f
method_impl::g(method_impl::Foo {}, method_impl::Foo {}); // $ method=g
method_non_parametric_impl::f(); // $ method=f
method_non_parametric_trait_impl::f(); // $ method=f
function_trait_bounds::f(); // $ method=f
trait_associated_type::f(); // $ method=f
generic_enum::f(); // $ method=f
method_supertraits::f(); // $ method=f
function_trait_bounds_2::f(); // $ method=f
option_methods::f(); // $ method=f
method_call_type_conversion::f(); // $ method=f
trait_implicit_self_borrow::f(); // $ method=f
implicit_self_borrow::f(); // $ method=f
borrowed_typed::f(); // $ method=f
try_expressions::f(); // $ method=f
builtins::f(); // $ method=f
operators::f(); // $ method=f
async_::f(); // $ method=f
impl_trait::f(); // $ method=f
indexers::f(); // $ method=f
loops::f(); // $ method=f
macros::f(); // $ method=f
method_determined_by_argument_type::f(); // $ method=f
dereference::test(); // $ method=test
}

View File

@@ -28,8 +28,10 @@ module ResolveTest implements TestSig {
source.fromSource() and
not source.isFromMacroExpansion()
|
target = resolveMethodCallTarget(source) and
target = source.(Call).getStaticTarget() and
functionHasValue(target, value) and
// `isFromMacroExpansion` does not always work
not target.(Function).getName().getText() = ["panic_fmt", "_print", "format", "must_use"] and
tag = "method"
or
target = resolveStructFieldExpr(source) and