Rust: Add inline expectations test for type inference

This commit is contained in:
Simon Friis Vindum
2025-04-03 12:49:34 +02:00
parent b0c40111e7
commit d5d61dd8b3
6 changed files with 948 additions and 954 deletions

View File

@@ -5,6 +5,7 @@
*/
private import codeql.rust.elements.internal.generated.Function
private import codeql.rust.elements.Comment
/**
* INTERNAL: This module contains the customizable definition of `Function` and should not
@@ -26,5 +27,22 @@ module Impl {
*/
class Function extends Generated::Function {
override string toStringImpl() { result = "fn " + this.getName().getText() }
/**
* Gets a comment preceding this function.
*
* A comment is considered preceding if it occurs immediately before this
* function or if only other comments occur between the comment and this
* function.
*/
Comment getPrecedingComment() {
result.getLocation().getFile() = this.getLocation().getFile() and
// When a function is preceded by comments its start line is the line of
// the first comment. Hence all relevant comments are found by including
// comments from the start line and up to the line with the function
// name.
this.getLocation().getStartLine() <= result.getLocation().getStartLine() and
result.getLocation().getStartLine() <= this.getName().getLocation().getStartLine()
}
}
}

View File

@@ -5,16 +5,7 @@ private module InlineMadTestLang implements InlineMadTestLangSig {
class Callable = R::Function;
string getComment(R::Function callable) {
exists(R::Comment comment |
result = comment.getCommentText() and
comment.getLocation().getFile() = callable.getLocation().getFile() and
// When a function is preceded by comments its start line is the line of
// the first comment. Hence all relevant comments are found by including
// comments from the start line and up to the line with the function
// name.
callable.getLocation().getStartLine() <= comment.getLocation().getStartLine() and
comment.getLocation().getStartLine() <= callable.getName().getLocation().getStartLine()
)
result = callable.getPrecedingComment().getCommentText()
}
}

View File

@@ -9,6 +9,6 @@ trait T1<T>: T2<S<T>> {
trait T2<T>: T1<S<T>> {
fn bar(self) {
self.foo()
self.foo() // $ method=foo
}
}

View File

@@ -24,36 +24,36 @@ mod field_access {
fn simple_field_access() {
let x = MyThing { a: S };
println!("{:?}", x.a);
println!("{:?}", x.a); // $ fieldof=MyThing
}
fn generic_field_access() {
// Explicit type argument
let x = GenericThing::<S> { a: S };
println!("{:?}", x.a);
let x = GenericThing::<S> { a: S }; // $ type=x:A.S
println!("{:?}", x.a); // $ fieldof=GenericThing
// Implicit type argument
let y = GenericThing { a: S };
println!("{:?}", x.a);
println!("{:?}", x.a); // $ fieldof=GenericThing
// The type of the field `a` can only be inferred from the concrete type
// in the struct declaration.
let x = OptionS {
a: MyOption::MyNone(),
};
println!("{:?}", x.a);
println!("{:?}", x.a); // $ fieldof=OptionS
// The type of the field `a` can only be inferred from the type argument
let x = GenericThing::<MyOption<S>> {
a: MyOption::MyNone(),
};
println!("{:?}", x.a);
println!("{:?}", x.a); // $ fieldof=GenericThing
let mut x = GenericThing {
a: MyOption::MyNone(),
};
// Only after this access can we infer the type parameter of `x`
let a: MyOption<S> = x.a;
let a: MyOption<S> = x.a; // $ fieldof=GenericThing
println!("{:?}", a);
}
@@ -85,8 +85,8 @@ mod method_impl {
pub fn g(x: Foo, y: Foo) -> Foo {
println!("main.rs::m1::g");
x.m1();
y.m2()
x.m1(); // $ method=m1
y.m2() // $ method=m2
}
}
@@ -102,20 +102,22 @@ mod method_non_parametric_impl {
struct S2;
impl MyThing<S1> {
// MyThing<S1>::m1
fn m1(self) -> S1 {
self.a
self.a // $ fieldof=MyThing
}
}
impl MyThing<S2> {
// MyThing<S2>::m1
fn m1(self) -> Self {
Self { a: self.a }
Self { a: self.a } // $ fieldof=MyThing
}
}
impl<T> MyThing<T> {
fn m2(self) -> T {
self.a
self.a // $ fieldof=MyThing
}
}
@@ -124,17 +126,17 @@ mod method_non_parametric_impl {
let y = MyThing { a: S2 };
// simple field access
println!("{:?}", x.a);
println!("{:?}", y.a);
println!("{:?}", x.a); // $ fieldof=MyThing
println!("{:?}", y.a); // $ fieldof=MyThing
println!("{:?}", x.m1()); // missing call target
println!("{:?}", y.m1().a); // missing call target
println!("{:?}", x.m1()); // $ MISSING: method=MyThing<S1>::m1
println!("{:?}", y.m1().a); // $ MISSING: method=MyThing<S2>::m1, field=MyThing
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
println!("{:?}", x.m2());
println!("{:?}", y.m2());
println!("{:?}", x.m2()); // $ method=m2
println!("{:?}", y.m2()); // $ method=m2
}
}
@@ -161,18 +163,20 @@ mod method_non_parametric_trait_impl {
}
fn call_trait_m1<T1, T2: MyTrait<T1>>(x: T2) -> T1 {
x.m1()
x.m1() // $ method=m1
}
impl MyTrait<S1> for MyThing<S1> {
// MyThing<S1>::m1
fn m1(self) -> S1 {
self.a
self.a // $ fieldof=MyThing
}
}
impl MyTrait<Self> for MyThing<S2> {
// MyThing<S2>::m1
fn m1(self) -> Self {
Self { a: self.a }
Self { a: self.a } // $ fieldof=MyThing
}
}
@@ -180,14 +184,14 @@ mod method_non_parametric_trait_impl {
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
println!("{:?}", x.m1()); // missing call target
println!("{:?}", y.m1().a); // missing call target
println!("{:?}", x.m1()); // $ MISSING: method=MyThing<S1>::m1
println!("{:?}", y.m1().a); // $ MISSING: method=MyThing<S2>::m1, field=MyThing
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
println!("{:?}", call_trait_m1(x)); // missing
println!("{:?}", call_trait_m1(y).a); // missing
println!("{:?}", call_trait_m1(x)); // MISSING: type=call_trait_m1(...):S1
println!("{:?}", call_trait_m1(y).a); // MISSING: field=MyThing
}
}
@@ -203,32 +207,34 @@ mod type_parameter_bounds {
// Two traits with the same method name.
trait FirstTrait<FT> {
// FirstTrait::method
fn method(self) -> FT;
}
trait SecondTrait<ST> {
// SecondTrait::method
fn method(self) -> ST;
}
fn call_first_trait_per_bound<I: Debug, T: SecondTrait<I>>(x: T) {
// The type parameter bound determines which method this call is resolved to.
let s1 = x.method();
let s1 = x.method(); // $ method=SecondTrait::method
println!("{:?}", s1);
}
fn call_second_trait_per_bound<I: Debug, T: SecondTrait<I>>(x: T) {
// The type parameter bound determines which method this call is resolved to.
let s2 = x.method();
let s2 = x.method(); // $ method=SecondTrait::method
println!("{:?}", s2);
}
fn trait_bound_with_type<T: FirstTrait<S1>>(x: T) {
let s = x.method();
println!("{:?}", s);
let s = x.method(); // $ method=FirstTrait::method
println!("{:?}", s); // $ type=s:S1
}
fn trait_per_bound_with_type<T: FirstTrait<S1>>(x: T) {
let s = x.method();
let s = x.method(); // $ method=FirstTrait::method
println!("{:?}", s);
}
@@ -240,15 +246,15 @@ mod type_parameter_bounds {
fn call_trait_per_bound_with_type_1<T: Pair<S1, S2>>(x: T, y: T) {
// The type in the type parameter bound determines the return type.
let s1 = x.fst();
let s2 = y.snd();
let s1 = x.fst(); // $ method=fst
let s2 = y.snd(); // $ method=snd
println!("{:?}, {:?}", s1, s2);
}
fn call_trait_per_bound_with_type_2<T2: Debug, T: Pair<S1, T2>>(x: T, y: T) {
// The type in the type parameter bound determines the return type.
let s1 = x.fst();
let s2 = y.snd();
let s1 = x.fst(); // $ method=fst
let s2 = y.snd(); // $ method=snd
println!("{:?}, {:?}", s1, s2);
}
}
@@ -271,23 +277,23 @@ mod function_trait_bounds {
where
Self: Sized,
{
self.m1()
self.m1() // $ method=m1
}
}
// 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()
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()
x.a.m1() // $ fieldof=MyThing method=m1
}
impl<T> MyTrait<T> for MyThing<T> {
fn m1(self) -> T {
self.a
self.a // $ fieldof=MyThing
}
}
@@ -295,14 +301,14 @@ mod function_trait_bounds {
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
println!("{:?}", x.m1());
println!("{:?}", y.m1());
println!("{:?}", x.m1()); // $ method=m1
println!("{:?}", y.m1()); // $ method=m1
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
println!("{:?}", x.m2());
println!("{:?}", y.m2());
println!("{:?}", x.m2()); // $ method=m2
println!("{:?}", y.m2()); // $ method=m2
let x2 = MyThing { a: S1 };
let y2 = MyThing { a: S2 };
@@ -343,6 +349,7 @@ mod trait_associated_type {
impl MyTrait for S {
type AssociatedType = S;
// S::m1
fn m1(self) -> Self::AssociatedType {
S
}
@@ -350,10 +357,10 @@ mod trait_associated_type {
pub fn f() {
let x = S;
println!("{:?}", x.m1());
println!("{:?}", x.m1()); // $ method=S::m1
let x = S;
println!("{:?}", x.m2()); // missing
println!("{:?}", x.m2()); // $ method=m2
}
}
@@ -382,8 +389,8 @@ mod generic_enum {
let x = MyEnum::C1(S1);
let y = MyEnum::C2 { a: S2 };
println!("{:?}", x.m1());
println!("{:?}", y.m1());
println!("{:?}", x.m1()); // $ method=m1
println!("{:?}", y.m1()); // $ method=m1
}
}
@@ -404,6 +411,7 @@ mod method_supertraits {
struct S2;
trait MyTrait1<Tr1> {
// MyTrait1::m1
fn m1(self) -> Tr1;
}
@@ -413,7 +421,7 @@ mod method_supertraits {
Self: Sized,
{
if 1 + 1 > 2 {
self.m1()
self.m1() // $ method=MyTrait1::m1
} else {
Self::m1(self)
}
@@ -426,24 +434,26 @@ mod method_supertraits {
Self: Sized,
{
if 1 + 1 > 2 {
self.m2().a
self.m2().a // $ method=m2 $ fieldof=MyThing
} else {
Self::m2(self).a
Self::m2(self).a // $ fieldof=MyThing
}
}
}
impl<T> MyTrait1<T> for MyThing<T> {
// MyThing::m1
fn m1(self) -> T {
self.a
self.a // $ fieldof=MyThing
}
}
impl<T> MyTrait2<T> for MyThing<T> {}
impl<T> MyTrait1<MyThing<T>> for MyThing2<T> {
// MyThing2::m1
fn m1(self) -> MyThing<T> {
MyThing { a: self.a }
MyThing { a: self.a } // $ fieldof=MyThing2
}
}
@@ -455,20 +465,20 @@ mod method_supertraits {
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
println!("{:?}", x.m1());
println!("{:?}", y.m1());
println!("{:?}", x.m1()); // $ method=MyThing::m1
println!("{:?}", y.m1()); // $ method=MyThing::m1
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
println!("{:?}", x.m2());
println!("{:?}", y.m2());
println!("{:?}", x.m2()); // $ method=m2
println!("{:?}", y.m2()); // $ method=m2
let x = MyThing2 { a: S1 };
let y = MyThing2 { a: S2 };
println!("{:?}", x.m3());
println!("{:?}", y.m3());
println!("{:?}", x.m3()); // $ method=m3
println!("{:?}", y.m3()); // $ method=m3
}
}
@@ -572,14 +582,16 @@ mod option_methods {
}
trait MyTrait<S> {
// MyTrait::set
fn set(&mut self, value: S);
fn call_set(&mut self, value: S) {
self.set(value);
self.set(value); // $ method=MyTrait::set
}
}
impl<T> MyTrait<T> for MyOption<T> {
// MyOption::set
fn set(&mut self, value: T) {}
}
@@ -602,15 +614,15 @@ mod option_methods {
struct S;
pub fn f() {
let x1 = MyOption::<S>::new(); // `::new` missing type `S`
let x1 = MyOption::<S>::new(); // $ MISSING: type=x1:T.S
println!("{:?}", x1);
let mut x2 = MyOption::new();
x2.set(S);
x2.set(S); // $ method=MyOption::set
println!("{:?}", x2);
let mut x3 = MyOption::new(); // missing type `S` from `MyOption<S>` (but can resolve `MyTrait<S>`)
x3.call_set(S);
x3.call_set(S); // $ method=call_set
println!("{:?}", x3);
let mut x4 = MyOption::new();
@@ -618,7 +630,7 @@ mod option_methods {
println!("{:?}", x4);
let x5 = MyOption::MySome(MyOption::<S>::MyNone());
println!("{:?}", x5.flatten()); // missing call target
println!("{:?}", x5.flatten()); // MISSING: method=flatten
let x6 = MyOption::MySome(MyOption::<S>::MyNone());
println!("{:?}", MyOption::<MyOption<S>>::flatten(x6));
@@ -656,26 +668,26 @@ mod method_call_type_conversion {
impl<T> S<T> {
fn m1(self) -> T {
self.0
self.0 // $ fieldof=S
}
fn m2(&self) -> &T {
&self.0
&self.0 // $ fieldof=S
}
fn m3(self: &S<T>) -> &T {
&self.0
&self.0 // $ fieldof=S
}
}
pub fn f() {
let x1 = S(S2);
println!("{:?}", x1.m1());
println!("{:?}", x1.m1()); // $ method=m1
let x2 = S(S2);
// implicit borrow
println!("{:?}", x2.m2());
println!("{:?}", x2.m3());
println!("{:?}", x2.m2()); // $ method=m2
println!("{:?}", x2.m3()); // $ method=m3
let x3 = S(S2);
// explicit borrow
@@ -684,32 +696,35 @@ mod method_call_type_conversion {
let x4 = &S(S2);
// explicit borrow
println!("{:?}", x4.m2());
println!("{:?}", x4.m3());
println!("{:?}", x4.m2()); // $ method=m2
println!("{:?}", x4.m3()); // $ method=m3
let x5 = &S(S2);
// implicit dereference
println!("{:?}", x5.m1());
println!("{:?}", x5.0);
println!("{:?}", x5.m1()); // $ method=m1
println!("{:?}", x5.0); // $ fieldof=S
let x6 = &S(S2);
// explicit dereference
println!("{:?}", (*x6).m1());
println!("{:?}", (*x6).m1()); // $ method=m1
}
}
mod trait_implicit_self_borrow {
trait MyTrait {
// MyTrait::foo
fn foo(&self) -> &Self;
// MyTrait::bar
fn bar(&self) -> &Self {
self.foo()
self.foo() // $ method=MyTrait::foo
}
}
struct MyStruct;
impl MyTrait for MyStruct {
// MyStruct::foo
fn foo(&self) -> &MyStruct {
self
}
@@ -717,7 +732,7 @@ mod trait_implicit_self_borrow {
pub fn f() {
let x = MyStruct;
x.bar();
x.bar(); // $ method=MyTrait::bar
}
}
@@ -734,7 +749,7 @@ mod implicit_self_borrow {
pub fn f() {
let x = MyStruct(S);
x.foo();
x.foo(); // $ method=foo
}
}
@@ -761,8 +776,8 @@ mod borrowed_typed {
pub fn f() {
let x = S {};
x.f1();
x.f2();
x.f1(); // $ method=f1
x.f2(); // $ method=f2
S::f3(&x);
}
}

View File

@@ -1,18 +1,60 @@
import rust
import utils.test.InlineExpectationsTest
import codeql.rust.internal.TypeInference as TypeInference
import TypeInference
import utils.test.InlineExpectationsTest
query predicate inferType(AstNode n, TypePath path, Type t) {
t = TypeInference::inferType(n, path)
}
query predicate resolveMethodCallExpr(MethodCallExpr mce, Function f) {
f = resolveMethodCallExpr(mce)
module ResolveTest implements TestSig {
string getARelevantTag() { result = ["method", "fieldof"] }
private predicate functionHasValue(Function f, string value) {
f.getPrecedingComment().getCommentText() = value
or
not exists(f.getPrecedingComment()) and
value = f.getName().getText()
}
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(AstNode source, AstNode target |
location = source.getLocation() and
element = source.toString()
|
target = resolveMethodCallExpr(source) and
functionHasValue(target, value) and
tag = "method"
or
target = resolveStructFieldExpr(source) and
any(Struct s | s.getStructField(_) = target).getName().getText() = value and
tag = "fieldof"
or
target = resolveTupleFieldExpr(source) and
any(Struct s | s.getTupleField(_) = target).getName().getText() = value and
tag = "fieldof"
)
}
}
query predicate resolveFieldExpr(FieldExpr fe, AstNode target) {
target = resolveStructFieldExpr(fe)
or
target = resolveTupleFieldExpr(fe)
module TypeTest implements TestSig {
string getARelevantTag() { result = "type" }
predicate tagIsOptional(string expectedTag) { expectedTag = "type" }
predicate hasActualResult(Location location, string element, string tag, string value) { none() }
predicate hasOptionalResult(Location location, string element, string tag, string value) {
tag = "type" and
exists(AstNode n, TypePath path, Type t |
t = TypeInference::inferType(n, path) and
location = n.getLocation() and
element = n.toString() and
if path.isEmpty()
then value = element + ":" + t
else value = element + ":" + path.toString() + "." + t.toString()
)
}
}
import MakeTest<MergeTests<ResolveTest, TypeTest>>