Skip to content

Rust: Add type inference for overloaded index expressions #19833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 24, 2025
Merged
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
16 changes: 12 additions & 4 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,13 @@ final class ArgumentPosition extends ParameterPosition {
* as the synthetic `ReceiverNode` is the argument for the `self` parameter.
*/
predicate isArgumentForCall(ExprCfgNode arg, CallCfgNode call, ParameterPosition pos) {
call.getPositionalArgument(pos.getPosition()) = arg
or
call.getReceiver() = arg and pos.isSelf() and not call.getCall().receiverImplicitlyBorrowed()
// TODO: Handle index expressions as calls in data flow.
not call.getCall() instanceof IndexExpr and
(
call.getPositionalArgument(pos.getPosition()) = arg
or
call.getReceiver() = arg and pos.isSelf() and not call.getCall().receiverImplicitlyBorrowed()
)
}

/** Provides logic related to SSA. */
Expand Down Expand Up @@ -959,7 +963,11 @@ private module Cached {

cached
newtype TDataFlowCall =
TCall(CallCfgNode c) { Stages::DataFlowStage::ref() } or
TCall(CallCfgNode c) {
Stages::DataFlowStage::ref() and
// TODO: Handle index expressions as calls in data flow.
not c.getCall() instanceof IndexExpr
} or
TSummaryCall(
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
) {
Expand Down
6 changes: 5 additions & 1 deletion rust/ql/lib/codeql/rust/dataflow/internal/Node.qll
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,11 @@ newtype TNode =
getPostUpdateReverseStep(any(PostUpdateNode n).getPreUpdateNode().asExpr(), _)
]
} or
TReceiverNode(CallCfgNode mc, Boolean isPost) { mc.getCall().receiverImplicitlyBorrowed() } or
TReceiverNode(CallCfgNode mc, Boolean isPost) {
mc.getCall().receiverImplicitlyBorrowed() and
// TODO: Handle index expressions as calls in data flow.
not mc.getCall() instanceof IndexExpr
} or
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TClosureSelfReferenceNode(CfgScope c) { lambdaCreationExpr(c, _) } or
Expand Down
16 changes: 16 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/CallImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,20 @@ module Impl {
pos.asPosition() = 0 and result = super.getOperand(1)
}
}

private class IndexCall extends Call instanceof IndexExpr {
override string getMethodName() { result = "index" }

override Trait getTrait() { result.getCanonicalPath() = "core::ops::index::Index" }

override predicate implicitBorrowAt(ArgumentPosition pos, boolean certain) {
pos.isSelf() and certain = true
}

override Expr getArgument(ArgumentPosition pos) {
pos.isSelf() and result = super.getBase()
or
pos.asPosition() = 0 and result = super.getIndex()
}
}
}
75 changes: 39 additions & 36 deletions rust/ql/lib/codeql/rust/internal/TypeInference.qll
Original file line number Diff line number Diff line change
Expand Up @@ -772,45 +772,48 @@ private Type inferCallExprBaseType(AstNode n, TypePath path) {
n = a.getNodeAt(apos) and
result = CallExprBaseMatching::inferAccessType(a, apos, path0)
|
(
if
Copy link
Preview

Copilot AI Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The nested if/then/else block handling both DerefExpr and IndexExpr is complex; consider extracting this into a helper predicate or simplifying the condition to improve readability.

Copilot uses AI. Check for mistakes.

apos.isBorrowed(true)
or
// The desugaring of the unary `*e` is `*Deref::deref(&e)`. To handle the
// deref expression after the call we must strip a `&` from the type at
// the return position.
apos.isReturn() and a instanceof DerefExpr
) and
path0.isCons(TRefTypeParameter(), path)
or
apos.isBorrowed(false) and
exists(Type argType | argType = inferType(n) |
if argType = TRefType()
// The desugaring of the unary `*e` is `*Deref::deref(&e)` and the
// desugaring of `a[b]` is `*Index::index(&a, b)`. To handle the deref
// expression after the call we must strip a `&` from the type at the
// return position.
apos.isReturn() and
(a instanceof DerefExpr or a instanceof IndexExpr)
then path0.isCons(TRefTypeParameter(), path)
else
if apos.isBorrowed(false)
then
path = path0 and
path0.isCons(TRefTypeParameter(), _)
or
// adjust for implicit deref
not path0.isCons(TRefTypeParameter(), _) and
not (path0.isEmpty() and result = TRefType()) and
path = TypePath::cons(TRefTypeParameter(), path0)
else (
not (
argType.(StructType).asItemNode() instanceof StringStruct and
result.(StructType).asItemNode() instanceof Builtins::Str
) and
(
not path0.isCons(TRefTypeParameter(), _) and
not (path0.isEmpty() and result = TRefType()) and
path = path0
or
// adjust for implicit borrow
path0.isCons(TRefTypeParameter(), path)
exists(Type argType | argType = inferType(n) |
if argType = TRefType()
then
path = path0 and
path0.isCons(TRefTypeParameter(), _)
or
// adjust for implicit deref
not path0.isCons(TRefTypeParameter(), _) and
not (path0.isEmpty() and result = TRefType()) and
path = TypePath::cons(TRefTypeParameter(), path0)
else (
not (
argType.(StructType).asItemNode() instanceof StringStruct and
result.(StructType).asItemNode() instanceof Builtins::Str
) and
(
not path0.isCons(TRefTypeParameter(), _) and
not (path0.isEmpty() and result = TRefType()) and
path = path0
or
// adjust for implicit borrow
path0.isCons(TRefTypeParameter(), path)
)
)
)
else (
not apos.isBorrowed(_) and
path = path0
)
)
or
not apos.isBorrowed(_) and
path = path0
)
}

Expand Down Expand Up @@ -1116,8 +1119,8 @@ private class Vec extends Struct {
*/
pragma[nomagic]
private Type inferIndexExprType(IndexExpr ie, TypePath path) {
// TODO: Should be implemented as method resolution, using the special
// `std::ops::Index` trait.
// TODO: Method resolution to the `std::ops::Index` trait can handle the
// `Index` instances for slices and arrays.
exists(TypePath exprPath, Builtins::BuiltinType t |
TStruct(t) = inferType(ie.getIndex()) and
(
Expand Down
14 changes: 10 additions & 4 deletions rust/ql/test/library-tests/type-inference/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1856,21 +1856,27 @@ mod indexers {

// MyVec::index
fn index(&self, index: usize) -> &Self::Output {
&self.data[index] // $ fieldof=MyVec
&self.data[index] // $ fieldof=MyVec method=index
}
}

fn analyze_slice(slice: &[S]) {
let x = slice[0].foo(); // $ method=foo type=x:S
// NOTE: `slice` gets the spurious type `[]` because the desugaring of
// the index expression adds an implicit borrow. `&slice` has the type
// `&&[S]`, but the `index` methods takes a `&[S]`, so Rust adds an
// implicit dereference. We cannot currently handle a position that is
// both implicitly dereferenced and implicitly borrowed, so the extra
// type sneaks in.
let x = slice[0].foo(); // $ method=foo type=x:S method=index SPURIOUS: type=slice:[]
}

pub fn f() {
let mut vec = MyVec::new(); // $ type=vec:T.S
vec.push(S); // $ method=push
vec[0].foo(); // $ MISSING: method=foo -- type inference does not support the `Index` trait yet
vec[0].foo(); // $ method=MyVec::index method=foo

let xs: [S; 1] = [S];
let x = xs[0].foo(); // $ method=foo type=x:S
let x = xs[0].foo(); // $ method=foo type=x:S method=index

analyze_slice(&xs);
}
Expand Down
Loading