-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Rust: Support blanket implementations #20133
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -205,6 +205,13 @@ private module Input2 implements InputSig2 { | |
constraint = object.getTrait() | ||
) | ||
} | ||
|
||
private predicate testConditionSatisfiesConstraint( | ||
TypeAbstraction abs, TypeMention condition, TypeMention constraint | ||
) { | ||
conditionSatisfiesConstraint(abs, condition, constraint) and | ||
constraint.resolveType().(TraitType).getTrait().getName().getText() = "TryFuture" | ||
} | ||
} | ||
|
||
private module M2 = Make2<Input2>; | ||
|
@@ -1512,6 +1519,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int | |
methodCandidate(type, name, arity, impl) | ||
} | ||
|
||
/** | ||
* Holds if `mc` has `rootType` as the root type of the reciever and the target | ||
* method is named `name` and has arity `arity` | ||
*/ | ||
pragma[nomagic] | ||
private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) { | ||
rootType = mc.getTypeAt(TypePath::nil()) and | ||
|
@@ -1710,6 +1721,131 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) { | |
else any() | ||
} | ||
|
||
private module BlanketImplementation { | ||
/** | ||
* Holds if `impl` is a blanket implementation, that is, an implementation of a | ||
* trait for a type parameter. | ||
*/ | ||
private TypeParamItemNode getBlanketImplementationTypeParam(Impl impl) { | ||
result = impl.(ImplItemNode).resolveSelfTy() and | ||
result = impl.getGenericParamList().getAGenericParam() and | ||
not exists(impl.getAttributeMacroExpansion()) | ||
} | ||
|
||
predicate isBlanketImplementation(Impl impl) { exists(getBlanketImplementationTypeParam(impl)) } | ||
|
||
predicate duplicatedImpl(Impl impl1, Impl impl2) { | ||
isBlanketImplementation(impl1) and | ||
isBlanketImplementation(impl2) and | ||
impl1 != impl2 and | ||
getBlanketImplementationTypeParam(impl1).toString() = | ||
getBlanketImplementationTypeParam(impl2).toString() and | ||
impl1.getTrait().toString() = impl2.getTrait().toString() and | ||
impl1.getLocation().getFile() != impl2.getLocation().getFile() and | ||
impl1.getLocation().getFile().getBaseName() = impl2.getLocation().getFile().getBaseName() | ||
} | ||
|
||
Impl getCanonicalImpl(Impl impl) { | ||
isBlanketImplementation(impl) and | ||
result = | ||
max(Impl impl0 | | ||
duplicatedImpl(impl, impl0) or impl = impl0 | ||
| | ||
impl0 order by impl0.getLocation().getFile().getAbsolutePath() | ||
) | ||
} | ||
|
||
predicate isCanonicalBlanketImplementation(Impl impl) { impl = getCanonicalImpl(impl) } | ||
|
||
/** | ||
* Holds if `impl` is a blanket implementation for a type parameter and the type | ||
* parameter must implement `trait`. | ||
*/ | ||
private predicate blanketImplementationTraitBound(Impl impl, Trait t) { | ||
t = | ||
rank[1](Trait trait, int i | | ||
trait = getBlanketImplementationTypeParam(impl).resolveBound(i) and | ||
// Exclude traits that are "trivial" in the sense that they are known to | ||
// not narrow things down very much. | ||
not trait.getName().getText() = | ||
[ | ||
"Sized", "Clone", "Fn", "FnOnce", "FnMut", | ||
// The auto traits | ||
"Send", "Sync", "Unpin", "UnwindSafe", "RefUnwindSafe" | ||
] | ||
| | ||
trait order by i | ||
) | ||
} | ||
|
||
private predicate blanketImplementationWithoutBound(Impl impl) { | ||
not exists(impl.getAttributeMacroExpansion()) and | ||
exists(TypeParam gp | | ||
gp = impl.getGenericParamList().getAGenericParam() and | ||
Comment on lines
+1779
to
+1784
Check warningCode scanning / CodeQL Selecting minimum element using `rank[1]` Warning
Using rank1 is an anti-pattern, use min(..) instead.
|
||
gp = impl.(ImplItemNode).resolveSelfTy() and | ||
not exists(gp.(TypeParamItemNode).getABoundPath()) | ||
) | ||
Check warningCode scanning / CodeQL Dead code Warning
This code is never used, and it's not publicly exported.
|
||
} | ||
|
||
private predicate test(Impl impl, ItemNode resolved) { | ||
exists(TypeParamItemNode tp | | ||
tp = getBlanketImplementationTypeParam(impl) and | ||
not blanketImplementationTraitBound(impl, _) and | ||
not blanketImplementationWithoutBound(impl) and | ||
resolved = resolvePath(tp.getBoundPath(0)) | ||
) | ||
Check warningCode scanning / CodeQL Dead code Warning
This code is never used, and it's not publicly exported.
|
||
} | ||
|
||
private predicate blanketImplementationMethod( | ||
Impl impl, Trait trait, string name, int arity, Function f | ||
) { | ||
isCanonicalBlanketImplementation(impl) and | ||
blanketImplementationTraitBound(impl, trait) and | ||
f.getParamList().hasSelfParam() and | ||
arity = f.getParamList().getNumberOfParams() and | ||
// Make this stronger and document | ||
( | ||
f = impl.(ImplItemNode).getAssocItem(name) | ||
or | ||
f = impl.(ImplItemNode).resolveTraitTy().getAssocItem(name) and | ||
not f = impl.(ImplItemNode).getAssocItem(name) | ||
) and | ||
not getBlanketImplementationTypeParam(impl).resolveABound().(TraitItemNode).getASuccessor(name) = | ||
f | ||
} | ||
|
||
predicate methodCallMatchesBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) { | ||
// Only check method calls where we have ruled out inherent method targets. | ||
// Ideally we would also check if non-blanket method targets have been ruled | ||
// out. | ||
methodCallHasNoInherentTarget(mc) and | ||
exists(string name, int arity | | ||
isMethodCall(mc, t, name, arity) and | ||
blanketImplementationMethod(impl, trait, name, arity, f) | ||
) | ||
} | ||
|
||
module SatisfiesConstraintInput implements SatisfiesConstraintInputSig<MethodCall> { | ||
pragma[nomagic] | ||
predicate relevantConstraint(MethodCall mc, Type constraint) { | ||
methodCallMatchesBlanketImpl(mc, _, _, constraint.(TraitType).getTrait(), _) | ||
} | ||
|
||
predicate useUniversalConditions() { none() } | ||
} | ||
|
||
predicate getBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) { | ||
SatisfiesConstraint<MethodCall, SatisfiesConstraintInput>::satisfiesConstraintType(mc, | ||
Check warningCode scanning / CodeQL Dead code Warning
This code is never used, and it's not publicly exported.
|
||
TTrait(trait), _, _) and | ||
methodCallMatchesBlanketImpl(mc, t, impl, trait, f) | ||
} | ||
|
||
pragma[nomagic] | ||
Check warningCode scanning / CodeQL Predicates starting with "get" or "as" should return a value Warning
This predicate starts with 'get' but does not return a value.
|
||
Function getMethodFromBlanketImpl(MethodCall mc) { | ||
BlanketImplementation::getBlanketImpl(mc, _, _, _, result) | ||
} | ||
} | ||
|
||
/** Gets a method from an `impl` block that matches the method call `mc`. */ | ||
pragma[nomagic] | ||
private Function getMethodFromImpl(MethodCall mc) { | ||
|
@@ -1745,6 +1881,8 @@ private Function resolveMethodCallTarget(MethodCall mc) { | |
// The method comes from an `impl` block targeting the type of the receiver. | ||
result = getMethodFromImpl(mc) | ||
or | ||
result = BlanketImplementation::getMethodFromBlanketImpl(mc) | ||
or | ||
// The type of the receiver is a type parameter and the method comes from a | ||
// trait bound on the type parameter. | ||
result = getTypeParameterMethod(mc.getTypeAt(TypePath::nil()), mc.getMethodName()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Tests for method resolution targeting blanket trait implementations | ||
|
||
mod basic_blanket_impl { | ||
#[derive(Debug, Copy, Clone)] | ||
struct S1; | ||
|
||
trait Clone1 { | ||
fn clone1(&self) -> Self; | ||
} | ||
|
||
trait Duplicatable { | ||
fn duplicate(&self) -> Self | ||
where | ||
Self: Sized; | ||
} | ||
|
||
impl Clone1 for S1 { | ||
// S1::clone1 | ||
fn clone1(&self) -> Self { | ||
*self // $ target=deref | ||
} | ||
} | ||
|
||
// Blanket implementation for all types that implement Display and Clone | ||
impl<T: Clone1> Duplicatable for T { | ||
// Clone1duplicate | ||
fn duplicate(&self) -> Self { | ||
self.clone1() // $ target=clone1 | ||
} | ||
} | ||
|
||
pub fn test_basic_blanket() { | ||
let x = S1.clone1(); // $ target=S1::clone1 | ||
println!("{x:?}"); | ||
let y = S1.duplicate(); // $ MISSING: target=Clone1duplicate | ||
println!("{y:?}"); | ||
} | ||
} | ||
|
||
mod extension_trait_blanket_impl { | ||
// 1. Elements a trait that is implemented for a type parameter | ||
// 2. An extension trait | ||
// 3. A blanket implementation of the extension trait for a type parameter | ||
|
||
trait Flag { | ||
fn read_flag(&self) -> bool; | ||
} | ||
|
||
trait TryFlag { | ||
fn try_read_flag(&self) -> Option<bool>; | ||
} | ||
|
||
impl<Fl> TryFlag for Fl | ||
where | ||
Fl: Flag, | ||
{ | ||
fn try_read_flag(&self) -> Option<bool> { | ||
Some(self.read_flag()) // $ target=read_flag | ||
} | ||
} | ||
|
||
trait TryFlagExt: TryFlag { | ||
// TryFlagExt::try_read_flag_twice | ||
fn try_read_flag_twice(&self) -> Option<bool> { | ||
self.try_read_flag() // $ target=try_read_flag | ||
} | ||
} | ||
|
||
impl<T: TryFlag> TryFlagExt for T {} | ||
|
||
trait AnotherTryFlag { | ||
// AnotherTryFlag::try_read_flag_twice | ||
fn try_read_flag_twice(&self) -> Option<bool>; | ||
} | ||
|
||
struct MyTryFlag { | ||
flag: bool, | ||
} | ||
|
||
impl TryFlag for MyTryFlag { | ||
// MyTryFlag::try_read_flag | ||
fn try_read_flag(&self) -> Option<bool> { | ||
Some(self.flag) // $ fieldof=MyTryFlag | ||
} | ||
} | ||
|
||
struct MyFlag { | ||
flag: bool, | ||
} | ||
|
||
impl Flag for MyFlag { | ||
// MyFlag::read_flag | ||
fn read_flag(&self) -> bool { | ||
self.flag // $ fieldof=MyFlag | ||
} | ||
} | ||
|
||
struct MyOtherFlag { | ||
flag: bool, | ||
} | ||
|
||
impl AnotherTryFlag for MyOtherFlag { | ||
// MyOtherFlag::try_read_flag_twice | ||
fn try_read_flag_twice(&self) -> Option<bool> { | ||
Some(self.flag) // $ fieldof=MyOtherFlag | ||
} | ||
} | ||
|
||
fn test() { | ||
let my_try_flag = MyTryFlag { flag: true }; | ||
let result = my_try_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice | ||
|
||
let my_flag = MyFlag { flag: true }; | ||
// Here `TryFlagExt::try_read_flag_twice` is since there is a blanket | ||
// implementaton of `TryFlag` for `Flag`. | ||
let result = my_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice | ||
|
||
let my_other_flag = MyOtherFlag { flag: true }; | ||
// Here `TryFlagExt::try_read_flag_twice` is _not_ a target since | ||
// `MyOtherFlag` does not implement `TryFlag`. | ||
let result = my_other_flag.try_read_flag_twice(); // $ target=MyOtherFlag::try_read_flag_twice | ||
} | ||
} | ||
|
||
mod replicate { | ||
use std::{ | ||
fs::{create_dir, create_dir_all, read_dir, remove_dir_all}, | ||
io, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
pub type Result<T> = std::result::Result<T, io::Error>; | ||
|
||
#[rustfmt::skip] | ||
pub fn create<P>(path: P, erase: bool) -> Result<()> | ||
where | ||
P: AsRef<Path>, | ||
{ | ||
if erase && path.as_ref().exists() // $ target=as_ref MISSING: target=exists | ||
{ | ||
remove(&path)?; // $ target=remove | ||
} | ||
Ok(create_dir(&path)?) // $ target=create_dir | ||
} | ||
|
||
#[rustfmt::skip] | ||
pub fn remove<P: AsRef<Path>>(path: P) -> Result<()> { | ||
if path.as_ref().exists() // $ target=as_ref target=exists | ||
{ | ||
Ok(remove_dir_all(path)?) // $ target=remove_dir_all | ||
} else { | ||
Ok(()) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
#![feature(box_patterns)] | ||
|
||
mod blanket_impl; | ||
|
||
mod field_access { | ||
#[derive(Debug)] | ||
struct S; | ||
|
Check warning
Code scanning / CodeQL
Dead code Warning