Skip to content

Conversation

@hvitved
Copy link
Contributor

@hvitved hvitved commented Dec 5, 2025

Does what the PR title says, and additionally makes sure to prioritize shared borrows over mutable borrows when constructing candidate receiver types.

DCA is excellent; as expected, we see a large decrease in resolution inconsistencies, because we no longer have resolution overlap for methods with implementations for both shared and mutable borrows.

@github-actions github-actions bot added the Rust Pull requests that update Rust code label Dec 5, 2025
@hvitved hvitved force-pushed the rust/type-inference-distinguish-mut-ref branch 4 times, most recently from f8e7c13 to a74c4e5 Compare December 8, 2025 09:27
@hvitved hvitved force-pushed the rust/type-inference-distinguish-mut-ref branch 4 times, most recently from b13cf2b to c62c00a Compare December 11, 2025 10:37
@hvitved hvitved force-pushed the rust/type-inference-distinguish-mut-ref branch 2 times, most recently from 078f152 to d1eb4ff Compare December 12, 2025 15:22
@hvitved hvitved force-pushed the rust/type-inference-distinguish-mut-ref branch 3 times, most recently from 19551e3 to 5682de7 Compare December 15, 2025 09:12
@hvitved hvitved force-pushed the rust/type-inference-distinguish-mut-ref branch from 5682de7 to 99b0d0c Compare December 15, 2025 19:31
string derefChain, boolean borrow, TypePath path
) {
result = substituteLookupTraits(this.getACandidateReceiverTypeAt(derefChain, borrow, path))
Type getANonPseudoCandidateReceiverTypeAt(string derefChain, BorrowKind borrow, TypePath path) {

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for borrow, or derefChain, or path, but the QLDoc mentions unknown
@hvitved hvitved force-pushed the rust/type-inference-distinguish-mut-ref branch 6 times, most recently from 85c8f0d to 63e381c Compare December 17, 2025 15:45
@hvitved hvitved added the no-change-note-required This PR does not need a change note label Dec 17, 2025
@hvitved hvitved marked this pull request as ready for review December 17, 2025 16:42
@hvitved hvitved requested a review from a team as a code owner December 17, 2025 16:42
Copilot AI review requested due to automatic review settings December 17, 2025 16:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves Rust type inference by distinguishing between mutable references (&mut T) and shared references (&T), which were previously treated as a single RefType. The change significantly reduces path resolution inconsistencies by eliminating ambiguity when methods are implemented for both borrow types.

Key Changes:

  • Split RefType into RefSharedType and RefMutType in the type system
  • Updated method resolution to prioritize shared borrows over mutable borrows when constructing candidate receiver types
  • Enhanced type inference to track borrow mutability through patterns, reference expressions, and method calls

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
rust/ql/lib/codeql/rust/internal/Type.qll Refactored RefType into an abstract base class with RefSharedType and RefMutType subclasses
rust/ql/lib/codeql/rust/frameworks/stdlib/Builtins.qll Updated builtin type definitions to distinguish shared and mutable reference types
rust/ql/lib/codeql/rust/internal/TypeInference.qll Enhanced method resolution with BorrowKind enum to track shared/mutable borrows, prioritizing shared borrows in candidate generation
rust/ql/lib/codeql/rust/internal/TypeMention.qll Updated type mention resolution to correctly handle mutable vs shared references
rust/ql/lib/codeql/rust/internal/PathResolution.qll Updated builtin type resolution to distinguish reference types based on mutability
rust/ql/lib/codeql/rust/elements/internal/OperationImpl.qll Added TODO comment for handling DerefMut trait
rust/ql/test/**/*.expected Updated test expectations reflecting reduced path resolution inconsistencies
rust/ql/test/**/main.rs, dereference.rs, pattern_matching.rs Updated test annotations to reflect correct TRefMut types

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +33 to 34
// todo: handle `core::ops::deref::DerefMut`
op = "*" and path = "core::ops::deref::Deref" and method = "deref" and borrows = 1
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The TODO comment indicates that core::ops::deref::DerefMut is not yet handled. Since this PR introduces distinction between mutable and shared borrows, the dereference operator for mutable references should also be handled to properly support the DerefMut trait, which is used when dereferencing &mut T types. Without this, type inference for operations on mutable references may be incomplete.

Suggested change
// todo: handle `core::ops::deref::DerefMut`
op = "*" and path = "core::ops::deref::Deref" and method = "deref" and borrows = 1
op = "*" and path = "core::ops::deref::Deref" and method = "deref" and borrows = 1
or
op = "*" and path = "core::ops::deref::DerefMut" and method = "deref_mut" and borrows = 1

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@paldepind paldepind left a comment

Choose a reason for hiding this comment

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

Looks really great! I only have a bunch of nitpicky comments.

// forex using recursion
pragma[nomagic]
private predicate hasNoCompatibleNonBlanketTargetBorrowToIndex(
private predicate hasNoCompatibleTargetMutBorrowToIndex(
Copy link
Contributor

Choose a reason for hiding this comment

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

The code repetition here is a bit unfortunate, but I guess it's not that easy to get rid of.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I don't think we can easily get rid of the duplication.

pos.isSelf() and borrows >= 1
pos.isSelf() and
borrows >= 1 and
if this instanceof AssignmentOperation
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be

Suggested change
if this instanceof AssignmentOperation
if this instanceof CompoundAssignmentExpr

?

I understand why we want this for stuff like a += 1 which is equivalent to (&mut a).add_assign(1). But do we need it for normal assignments that (in some cases) are handled by SSA?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CompoundAssignmentExpr will do the same here because of the .isOverloaded restriction, but happy to change.


/** Holds if `n` is implicitly borrowed. */
cached
predicate implicitBorrow(AstNode n) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it might make sense to expose mutability here as well.

Suggested change
predicate implicitBorrow(AstNode n) {
predicate implicitBorrow(AstNode n, boolean mutable) {

That might be useful elsewhere. For instance in mutablyBorrows in SsaImpl.qll this

  e = any(MethodCallExpr mc).getReceiver() and
  e.(VariableAccess).getVariable() = v and
  v.isMutable()

is a rough approximation that could (in the future) benefit from using this predicate instead.

abstract class RefType extends StructType { }

pragma[nomagic]
TypeParamTypeParameter getRefTypeParameter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of having getRefTypeParameter, getRefMutTypeParameter, and getRefSharedTypeParameter we could have a single getRefTypeParameter(boolean mutable):

pragma[nomagic]
RefType getRefType(boolean mutable) {
  mutable = true and result = any(RefMutType t)
  or
  mutable = false and result = any(RefSharedType t)
}

pragma[nomagic]
TypeParamTypeParameter getRefTypeParameter(boolean mutable) {
  result = getRefType(mutable).getPositionalTypeParameter(0)
}

or
ref = any(RefPat rp | if rp.isMut() then isMut = true else isMut = false)
|
if isMut = true then result instanceof RefMutType else result instanceof RefSharedType
Copy link
Contributor

Choose a reason for hiding this comment

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

Using the getRefType suggested above.

Suggested change
if isMut = true then result instanceof RefMutType else result instanceof RefSharedType
result = getRefType(isMut)

Comment on lines 2028 to 2030
exists(BorrowKind borrow, RefType rt |
this.implicitBorrowAt(pos, borrow) and
rt = borrow.getRefType()
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
exists(BorrowKind borrow, RefType rt |
this.implicitBorrowAt(pos, borrow) and
rt = borrow.getRefType()
exists(boolean mutable, RefType rt |
this.implicitBorrowAt(pos, mutable) and
rt = getRefType(mutable)

Comment on lines 2045 to 2047
override predicate argumentHasImplicitBorrow(AstNode arg, BorrowKind borrow) {
exists(ArgumentPosition pos |
this.implicitBorrowAt(pos) and
this.implicitBorrowAt(pos, borrow) and
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
override predicate argumentHasImplicitBorrow(AstNode arg, BorrowKind borrow) {
exists(ArgumentPosition pos |
this.implicitBorrowAt(pos) and
this.implicitBorrowAt(pos, borrow) and
override predicate argumentHasImplicitBorrow(AstNode arg, boolean mutable) {
exists(ArgumentPosition pos |
this.implicitBorrowAt(pos, mutable) and

Comment on lines 1273 to 1301
private newtype TBorrowKind =
TNoBorrowKind() or
TSharedBorrowKind() or
TMutBorrowKind()

private class BorrowKind extends TBorrowKind {
predicate isNoBorrow() { this = TNoBorrowKind() }

RefType getRefType() {
this = TSharedBorrowKind() and
result instanceof RefSharedType
or
this = TMutBorrowKind() and
result instanceof RefMutType
}

string toString() {
this = TNoBorrowKind() and
result = ""
or
this = TSharedBorrowKind() and
result = "&"
or
this = TMutBorrowKind() and
result = "&mut"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps this

Suggested change
private newtype TBorrowKind =
TNoBorrowKind() or
TSharedBorrowKind() or
TMutBorrowKind()
private class BorrowKind extends TBorrowKind {
predicate isNoBorrow() { this = TNoBorrowKind() }
RefType getRefType() {
this = TSharedBorrowKind() and
result instanceof RefSharedType
or
this = TMutBorrowKind() and
result instanceof RefMutType
}
string toString() {
this = TNoBorrowKind() and
result = ""
or
this = TSharedBorrowKind() and
result = "&"
or
this = TMutBorrowKind() and
result = "&mut"
}
}
private newtype TBorrowKind =
TNoBorrowKind() or
TSomeBorrowKind(Boolean mutable)
private class BorrowKind extends TBorrowKind {
predicate isNoBorrow() { this = TNoBorrowKind() }
RefType getRefType() {
result = getRefType(any(boolean mutable | this = TSomeBorrowKind(mutable)))
}
string toString() {
this.isNoBorrow() and
result = ""
or
result = this.getRefType().toString()
}
}

or using Option.

Comment on lines 1902 to 1904
predicate argumentHasImplicitBorrow(AstNode arg, BorrowKind borrow) {
exists(this.resolveCallTarget(_, "", borrow)) and
borrow != TNoBorrowKind() and
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
predicate argumentHasImplicitBorrow(AstNode arg, BorrowKind borrow) {
exists(this.resolveCallTarget(_, "", borrow)) and
borrow != TNoBorrowKind() and
predicate argumentHasImplicitBorrow(AstNode arg, boolean mutable) {
exists(this.resolveCallTarget(_, "", TSomeBorrowKind(mutable))) and

// adjust for implicit borrow
apos.isSelf() and
derefChainBorrow = ";borrow" and
derefChainBorrow = [";&", ";&mut"] and
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
derefChainBorrow = [";&", ";&mut"] and
derefChainBorrow = encodeDerefChainBorrow("", TSomeBorrowKind(_)) and

To avoid hard coding the encoding too much and instead use encodeDerefChainBorrow as the source of truth for the encoding.

@hvitved hvitved force-pushed the rust/type-inference-distinguish-mut-ref branch from 63e381c to aae6cd9 Compare December 18, 2025 11:50
@hvitved hvitved requested a review from paldepind December 18, 2025 11:50
apos.isSelf() and
derefChainBorrow = ".ref;" and
path = TypePath::cons(getRefTypeParameter(), path0)
derefChainBorrow = MethodCallMatchingInput::encodeDerefChainBorrow(".ref", TNoBorrowKind()) and
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

Copy link
Contributor

@paldepind paldepind left a comment

Choose a reason for hiding this comment

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

Thanks for addressing my comments! Really great stuff 🚀

@hvitved hvitved merged commit 27874ca into github:main Dec 18, 2025
19 checks passed
@hvitved hvitved deleted the rust/type-inference-distinguish-mut-ref branch December 18, 2025 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-change-note-required This PR does not need a change note Rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants