Skip to content

Rust: Path resolution associated type fix #20096

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
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Rust: Avoid mixing up type parameters and associated types in path re…
…solution
  • Loading branch information
paldepind committed Jul 21, 2025
commit ac6715fb3a08f18cd5650e6496d57e423271e2c7
104 changes: 66 additions & 38 deletions rust/ql/lib/codeql/rust/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,18 @@ abstract class ItemNode extends Locatable {
result = this.(SourceFileItemNode).getSuper()
}

Copy link
Preview

Copilot AI Jul 21, 2025

Choose a reason for hiding this comment

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

The new getAChildSuccessor predicate lacks documentation explaining its purpose and relationship to the existing successor methods.

Suggested change
/**
* Gets a child item of this item that has the specified name.
*
* This predicate is a helper for resolving paths and is used in conjunction
* with other successor methods like `getADescendant` and `getImmediateParent`.
* It identifies a child item by matching its name and its immediate parent.
*/

Copilot uses AI. Check for mistakes.

pragma[nomagic]
private ItemNode getAChildSuccessor(string name) {
this = result.getImmediateParent() and
name = result.getName()
}

cached
ItemNode getASuccessorRec(string name) {
Stages::PathResolutionStage::ref() and
sourceFileEdge(this, name, result)
or
this = result.getImmediateParent() and
name = result.getName()
result = this.getAChildSuccessor(name)
or
fileImportEdge(this, name, result)
or
Expand Down Expand Up @@ -224,6 +229,38 @@ abstract class ItemNode extends Locatable {
result.(CrateItemNode).isPotentialDollarCrateTarget()
}

/**
* Holds if the successor `item` with the name `name` is not available locally
* for unqualified paths.
*
* This has the effect that a path of the form `name` inside `this` will not
* resolve to `item`.
Copy link
Preview

Copilot AI Jul 21, 2025

Choose a reason for hiding this comment

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

The excludedLocally predicate would benefit from more detailed documentation explaining the specific scenarios where associated items require Self:: qualification.

Suggested change
* resolve to `item`.
* resolve to `item`.
*
* ## Specific Scenarios
* In Rust, associated items (such as methods, associated types, or constants)
* defined within an `impl` or `trait` block are not directly accessible
* without qualification. To access these items, a `Self::` prefix is required.
*
* For example:
* ```
* impl MyTrait for MyType {
* fn my_method() {}
* }
*
* // Inside the `impl` block, `my_method` must be accessed as `Self::my_method`.
* ```
*
* This predicate identifies such cases where unqualified access is not allowed.
*
* ## References
* For more details, see the Rust reference on [associated items](https://doc.rust-lang.org/reference/items/associated-items.html).

Copilot uses AI. Check for mistakes.

*/
pragma[nomagic]
predicate excludedLocally(string name, ItemNode item) {
// Associated items in an impl or trait block are not directly available
// inside the block, they require a qualified path with a `Self` prefix.
item = this.getAChildSuccessor(name) and
this instanceof ImplOrTraitItemNode and
item instanceof AssocItemNode
}

/**
* Holds if the successor `item` with the name `name` is not available
* externally for qualified paths that resolve to this item.
*
* This has the effect that a path of the form `Qualifier::name`, where
* `Qualifier` resolves to this item, will not resolve to `item`.
Copy link
Preview

Copilot AI Jul 21, 2025

Choose a reason for hiding this comment

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

The excludedExternally predicate should include documentation with examples showing when type parameters are not accessible outside their declaring scope.

Suggested change
* `Qualifier` resolves to this item, will not resolve to `item`.
* `Qualifier` resolves to this item, will not resolve to `item`.
*
* ## Examples
*
* Consider the following Rust code:
*
* ```
* impl<T> MyStruct<T> {
* fn new() -> Self {
* // ...
* }
* }
* ```
*
* Here, the type parameter `T` is not accessible outside the `impl` block.
* For example, the following code would result in a compilation error:
*
* ```
* let x: MyStruct::T; // Error: `T` is not accessible here
* ```
*
* The `excludedExternally` predicate ensures that such type parameters are
* excluded from external path resolution.

Copilot uses AI. Check for mistakes.

*/
pragma[nomagic]
predicate excludedExternally(string name, ItemNode item) {
// Type parameters for an `impl` or trait block are not available outside of
// the block.
item = this.getAChildSuccessor(name) and
this instanceof ImplOrTraitItemNode and
item instanceof TypeParamItemNode
}

pragma[nomagic]
private predicate hasSourceFunction(string name) {
this.getASuccessorFull(name).(Function).fromSource()
Expand Down Expand Up @@ -1145,7 +1182,9 @@ pragma[nomagic]
private predicate declares(ItemNode item, Namespace ns, string name) {
exists(ItemNode child | child.getImmediateParent() = item |
child.getName() = name and
child.getNamespace() = ns
child.getNamespace() = ns and
// If `item` is excluded locally then it does not declare `name`.
not item.excludedLocally(name, child)
or
useTreeDeclares(child.(Use).getUseTree(), name) and
exists(ns) // `use foo::bar` can refer to both a value and a type
Expand Down Expand Up @@ -1193,38 +1232,27 @@ private ItemNode getOuterScope(ItemNode i) {
result = i.getImmediateParent()
}

pragma[nomagic]
private ItemNode getAdjustedEnclosing(ItemNode encl0, Namespace ns) {
// functions in `impl` blocks need to use explicit `Self::` to access other
// functions in the `impl` block
if encl0 instanceof ImplOrTraitItemNode and ns.isValue()
then result = encl0.getImmediateParent()
else result = encl0
}

/**
* Holds if the unqualified path `p` references an item named `name`, and `name`
* may be looked up in the `ns` namespace inside enclosing item `encl`.
*/
pragma[nomagic]
private predicate unqualifiedPathLookup(ItemNode encl, string name, Namespace ns, RelevantPath p) {
exists(ItemNode encl0 | encl = getAdjustedEnclosing(encl0, ns) |
// lookup in the immediately enclosing item
p.isUnqualified(name) and
encl0.getADescendant() = p and
exists(ns) and
not name = ["crate", "$crate", "super", "self"]
or
// lookup in an outer scope, but only if the item is not declared in inner scope
exists(ItemNode mid |
unqualifiedPathLookup(mid, name, ns, p) and
not declares(mid, ns, name) and
not (
name = "Self" and
mid = any(ImplOrTraitItemNode i).getAnItemInSelfScope()
) and
encl0 = getOuterScope(mid)
)
// lookup in the immediately enclosing item
p.isUnqualified(name) and
encl.getADescendant() = p and
exists(ns) and
not name = ["crate", "$crate", "super", "self"]
or
// lookup in an outer scope, but only if the item is not declared in inner scope
exists(ItemNode mid |
unqualifiedPathLookup(mid, name, ns, p) and
not declares(mid, ns, name) and
not (
name = "Self" and
mid = any(ImplOrTraitItemNode i).getAnItemInSelfScope()
) and
encl = getOuterScope(mid)
)
}

Expand All @@ -1245,10 +1273,10 @@ private predicate sourceFileHasCratePathTc(ItemNode i1, ItemNode i2) =

/**
* Holds if the unqualified path `p` references a keyword item named `name`, and
* `name` may be looked up in the `ns` namespace inside enclosing item `encl`.
* `name` may be looked up inside enclosing item `encl`.
*/
pragma[nomagic]
private predicate keywordLookup(ItemNode encl, string name, Namespace ns, RelevantPath p) {
private predicate keywordLookup(ItemNode encl, string name, RelevantPath p) {
// For `($)crate`, jump directly to the root module
exists(ItemNode i | p.isCratePath(name, i) |
encl instanceof SourceFile and
Expand All @@ -1259,18 +1287,17 @@ private predicate keywordLookup(ItemNode encl, string name, Namespace ns, Releva
or
name = ["super", "self"] and
p.isUnqualified(name) and
exists(ItemNode encl0 |
encl0.getADescendant() = p and
encl = getAdjustedEnclosing(encl0, ns)
)
encl.getADescendant() = p
}

pragma[nomagic]
private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns) {
exists(ItemNode encl, string name | result = getASuccessorFull(encl, name, ns) |
exists(ItemNode encl, string name |
result = getASuccessorFull(encl, name, ns) and not encl.excludedLocally(name, result)
|
unqualifiedPathLookup(encl, name, ns, p)
or
keywordLookup(encl, name, ns, p)
keywordLookup(encl, name, p) and exists(ns)
)
}

Expand All @@ -1291,7 +1318,8 @@ private ItemNode resolvePath0(RelevantPath path, Namespace ns) {
or
exists(ItemNode q, string name |
q = resolvePathQualifier(path, name) and
result = getASuccessorFull(q, name, ns)
result = getASuccessorFull(q, name, ns) and
not q.excludedExternally(name, result)
)
or
result = resolveUseTreeListItem(_, _, path) and
Expand Down
16 changes: 8 additions & 8 deletions rust/ql/test/library-tests/path-resolution/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,29 +661,29 @@ mod associated_types {
Error, // IError
> Reduce // $ item=IReduce
for MyImpl<
Input, // $ item=IInput SPURIOUS: item=IInputAssociated
Error, // $ item=IError SPURIOUS: item=IErrorAssociated
Input, // $ item=IInput
Error, // $ item=IError
> // $ item=MyImpl
{
type Input = Result<
Input, // $ item=IInput SPURIOUS: item=IInputAssociated
Self::Error, // $ item=IErrorAssociated SPURIOUS: item=IError
Input, // $ item=IInput
Self::Error, // $ item=IErrorAssociated
> // $ item=Result
; // IInputAssociated
type Error = Option<
Error // $ item=IError SPURIOUS: item=IErrorAssociated
Error // $ item=IError
> // $ item=Option
; // IErrorAssociated
type Output =
Input // $ item=IInput SPURIOUS: item=IInputAssociated
Input // $ item=IInput
; // IOutputAssociated

fn feed(
&mut self,
item: Self::Input // $ item=IInputAssociated SPURIOUS: item=IInput
item: Self::Input // $ item=IInputAssociated
) -> Result<
Self::Output, // $ item=IOutputAssociated
Self::Error // $ item=IErrorAssociated SPURIOUS: item=IError
Self::Error // $ item=IErrorAssociated
> { // $ item=Result
item
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,28 +297,20 @@ resolvePath
| main.rs:662:11:662:16 | Reduce | main.rs:643:5:651:5 | trait Reduce |
| main.rs:663:13:666:9 | MyImpl::<...> | main.rs:653:5:656:5 | struct MyImpl |
| main.rs:664:13:664:17 | Input | main.rs:660:13:660:17 | Input |
| main.rs:664:13:664:17 | Input | main.rs:668:9:672:9 | type Input |
| main.rs:665:13:665:17 | Error | main.rs:661:13:661:17 | Error |
| main.rs:665:13:665:17 | Error | main.rs:672:11:676:9 | type Error |
| main.rs:668:22:671:9 | Result::<...> | {EXTERNAL LOCATION} | enum Result |
| main.rs:669:13:669:17 | Input | main.rs:660:13:660:17 | Input |
| main.rs:669:13:669:17 | Input | main.rs:668:9:672:9 | type Input |
| main.rs:670:13:670:16 | Self | main.rs:658:5:690:5 | impl Reduce for MyImpl::<...> { ... } |
| main.rs:670:13:670:23 | ...::Error | main.rs:661:13:661:17 | Error |
| main.rs:670:13:670:23 | ...::Error | main.rs:672:11:676:9 | type Error |
| main.rs:673:22:675:9 | Option::<...> | {EXTERNAL LOCATION} | enum Option |
| main.rs:674:11:674:15 | Error | main.rs:661:13:661:17 | Error |
| main.rs:674:11:674:15 | Error | main.rs:672:11:676:9 | type Error |
| main.rs:678:13:678:17 | Input | main.rs:660:13:660:17 | Input |
| main.rs:678:13:678:17 | Input | main.rs:668:9:672:9 | type Input |
| main.rs:683:19:683:22 | Self | main.rs:658:5:690:5 | impl Reduce for MyImpl::<...> { ... } |
| main.rs:683:19:683:29 | ...::Input | main.rs:660:13:660:17 | Input |
| main.rs:683:19:683:29 | ...::Input | main.rs:668:9:672:9 | type Input |
| main.rs:684:14:687:9 | Result::<...> | {EXTERNAL LOCATION} | enum Result |
| main.rs:685:13:685:16 | Self | main.rs:658:5:690:5 | impl Reduce for MyImpl::<...> { ... } |
| main.rs:685:13:685:24 | ...::Output | main.rs:676:11:679:9 | type Output |
| main.rs:686:13:686:16 | Self | main.rs:658:5:690:5 | impl Reduce for MyImpl::<...> { ... } |
| main.rs:686:13:686:23 | ...::Error | main.rs:661:13:661:17 | Error |
| main.rs:686:13:686:23 | ...::Error | main.rs:672:11:676:9 | type Error |
| main.rs:693:5:693:7 | std | {EXTERNAL LOCATION} | Crate(std@0.0.0) |
| main.rs:693:11:693:14 | self | {EXTERNAL LOCATION} | Crate(std@0.0.0) |
Expand Down
2 changes: 1 addition & 1 deletion rust/ql/test/library-tests/type-inference/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2162,7 +2162,7 @@ mod loops {

for i in [1, 2, 3] {} // $ type=i:i32
for i in [1, 2, 3].map(|x| x + 1) {} // $ target=map MISSING: type=i:i32
for i in [1, 2, 3].into_iter() {} // $ target=into_iter MISSING: type=i:i32
for i in [1, 2, 3].into_iter() {} // $ target=into_iter type=i:i32

let vals1 = [1u8, 2, 3]; // $ type=vals1:[T;...].u8
for u in vals1 {} // $ type=u:u8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3526,8 +3526,12 @@ inferType
| main.rs:2164:22:2164:22 | 2 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2164:25:2164:25 | 3 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2164:40:2164:40 | 1 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2165:13:2165:13 | i | | {EXTERNAL LOCATION} | Item |
| main.rs:2165:13:2165:13 | i | | {EXTERNAL LOCATION} | i32 |
| main.rs:2165:18:2165:26 | [...] | | file://:0:0:0:0 | [] |
| main.rs:2165:18:2165:26 | [...] | [T;...] | {EXTERNAL LOCATION} | i32 |
| main.rs:2165:18:2165:38 | ... .into_iter() | | {EXTERNAL LOCATION} | IntoIter |
| main.rs:2165:18:2165:38 | ... .into_iter() | T | {EXTERNAL LOCATION} | i32 |
| main.rs:2165:19:2165:19 | 1 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2165:22:2165:22 | 2 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2165:25:2165:25 | 3 | | {EXTERNAL LOCATION} | i32 |
Expand Down Expand Up @@ -3739,11 +3743,8 @@ inferType
| main.rs:2226:39:2226:39 | 2 | | {EXTERNAL LOCATION} | u16 |
| main.rs:2226:42:2226:42 | 3 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2226:42:2226:42 | 3 | | {EXTERNAL LOCATION} | u16 |
| main.rs:2227:13:2227:13 | u | | {EXTERNAL LOCATION} | Vec |
| main.rs:2227:13:2227:13 | u | | {EXTERNAL LOCATION} | u16 |
| main.rs:2227:13:2227:13 | u | | file://:0:0:0:0 | & |
| main.rs:2227:13:2227:13 | u | A | {EXTERNAL LOCATION} | Global |
| main.rs:2227:13:2227:13 | u | T | {EXTERNAL LOCATION} | u16 |
| main.rs:2227:18:2227:23 | vals4a | | {EXTERNAL LOCATION} | Vec |
| main.rs:2227:18:2227:23 | vals4a | A | {EXTERNAL LOCATION} | Global |
| main.rs:2227:18:2227:23 | vals4a | T | {EXTERNAL LOCATION} | u16 |
Expand Down Expand Up @@ -3773,13 +3774,9 @@ inferType
| main.rs:2232:38:2232:38 | 2 | | {EXTERNAL LOCATION} | u32 |
| main.rs:2232:41:2232:41 | 3 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2232:41:2232:41 | 3 | | {EXTERNAL LOCATION} | u32 |
| main.rs:2233:13:2233:13 | u | | {EXTERNAL LOCATION} | Vec |
| main.rs:2233:13:2233:13 | u | | {EXTERNAL LOCATION} | i32 |
| main.rs:2233:13:2233:13 | u | | {EXTERNAL LOCATION} | u32 |
| main.rs:2233:13:2233:13 | u | | file://:0:0:0:0 | & |
| main.rs:2233:13:2233:13 | u | A | {EXTERNAL LOCATION} | Global |
| main.rs:2233:13:2233:13 | u | T | {EXTERNAL LOCATION} | i32 |
| main.rs:2233:13:2233:13 | u | T | {EXTERNAL LOCATION} | u32 |
| main.rs:2233:18:2233:22 | vals5 | | {EXTERNAL LOCATION} | Vec |
| main.rs:2233:18:2233:22 | vals5 | A | {EXTERNAL LOCATION} | Global |
| main.rs:2233:18:2233:22 | vals5 | T | {EXTERNAL LOCATION} | i32 |
Expand All @@ -3801,12 +3798,8 @@ inferType
| main.rs:2235:39:2235:39 | 2 | | {EXTERNAL LOCATION} | u64 |
| main.rs:2235:42:2235:42 | 3 | | {EXTERNAL LOCATION} | i32 |
| main.rs:2235:42:2235:42 | 3 | | {EXTERNAL LOCATION} | u64 |
| main.rs:2236:13:2236:13 | u | | {EXTERNAL LOCATION} | Vec |
| main.rs:2236:13:2236:13 | u | | file://:0:0:0:0 | & |
| main.rs:2236:13:2236:13 | u | &T | {EXTERNAL LOCATION} | u64 |
| main.rs:2236:13:2236:13 | u | A | {EXTERNAL LOCATION} | Global |
| main.rs:2236:13:2236:13 | u | T | file://:0:0:0:0 | & |
| main.rs:2236:13:2236:13 | u | T.&T | {EXTERNAL LOCATION} | u64 |
| main.rs:2236:18:2236:22 | vals6 | | {EXTERNAL LOCATION} | Vec |
| main.rs:2236:18:2236:22 | vals6 | A | {EXTERNAL LOCATION} | Global |
| main.rs:2236:18:2236:22 | vals6 | T | file://:0:0:0:0 | & |
Expand All @@ -3821,11 +3814,8 @@ inferType
| main.rs:2239:9:2239:13 | vals7 | A | {EXTERNAL LOCATION} | Global |
| main.rs:2239:9:2239:13 | vals7 | T | {EXTERNAL LOCATION} | u8 |
| main.rs:2239:20:2239:22 | 1u8 | | {EXTERNAL LOCATION} | u8 |
| main.rs:2240:13:2240:13 | u | | {EXTERNAL LOCATION} | Vec |
| main.rs:2240:13:2240:13 | u | | {EXTERNAL LOCATION} | u8 |
| main.rs:2240:13:2240:13 | u | | file://:0:0:0:0 | & |
| main.rs:2240:13:2240:13 | u | A | {EXTERNAL LOCATION} | Global |
| main.rs:2240:13:2240:13 | u | T | {EXTERNAL LOCATION} | u8 |
| main.rs:2240:18:2240:22 | vals7 | | {EXTERNAL LOCATION} | Vec |
| main.rs:2240:18:2240:22 | vals7 | A | {EXTERNAL LOCATION} | Global |
| main.rs:2240:18:2240:22 | vals7 | T | {EXTERNAL LOCATION} | u8 |
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy