Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest] # windows-latest
toolchain: [nightly-2025-11-17, nightly]
toolchain: [nightly-2026-01-01, nightly]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down
43 changes: 43 additions & 0 deletions spec-trait-impl/crates/spec-trait-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::fmt::Debug;
#[allow(clippy::upper_case_acronyms)]
struct ZST;
struct ZST2;
struct S<T>(T);

trait Foo<T> {
fn foo(&self, x: T);
Expand All @@ -18,6 +19,10 @@ trait Foo3<T> {
fn foo(&self, x: T, y: String);
}

trait Foo4 {
fn foo(&self);
}

type MyType = u8;
type MyVecAlias = Vec<i32>;

Expand Down Expand Up @@ -106,6 +111,13 @@ impl<T> Foo<T> for ZST {
}
}

#[when(T = fn(&u8) -> &u8)]
impl<T> Foo<T> for ZST {
fn foo(&self, _x: T) {
println!("Foo impl ZST where T is a function pointer from &u8 to &u8");
}
}

// ZST - Foo2

impl<T, U> Foo2<T, U> for ZST {
Expand Down Expand Up @@ -239,10 +251,33 @@ impl<T, U> Foo<U> for T {
}
}

// T - Foo4
impl<T> Foo4 for T {
fn foo(&self) {
println!("Default Foo4 for T");
}
}

#[when(T = i32)]
impl<T> Foo4 for S<T> {
fn foo(&self) {
println!("Foo4 impl S<T> where T is i32");
}
}

#[when(T = i32)]
impl<T> Foo4 for Vec<T> {
fn foo(&self) {
println!("Foo4 impl Vec<T> where T is i32");
}
}

fn main() {
let zst = ZST;
let zst2 = ZST2;
let x = vec![1i32];
let s1 = S(1i32);
let s2 = S(1u8);

// ZST - Foo
spec! { zst.foo(1u8); ZST; [u8]; u8 = MyType } // -> "Foo impl ZST where T is MyType"
Expand All @@ -258,6 +293,7 @@ fn main() {
spec! { zst.foo(&1i32); ZST; [&i32] } // -> "Foo impl ZST where T is &'a _"
spec! { zst.foo(1i32); ZST; [i32]; i32: Bar } // -> "Foo impl ZST where T implements Bar"
spec! { zst.foo(1i64); ZST; [i64]; i64: Bar + FooBar } // -> "Foo impl ZST where T implements Bar and FooBar"
spec! { zst.foo(|x: &u8| x); ZST; [fn(&u8) -> &u8] } // -> "Foo impl ZST where T is a function pointer from &u8 to &u8"
spec! { zst.foo(1i8); ZST; [i8] } // -> "Default Foo for ZST"
println!();

Expand Down Expand Up @@ -291,4 +327,11 @@ fn main() {
spec! { 1i32.foo("str"); i32; [&str] } // -> "Foo impl T where U is &str"
spec! { zst.foo("str"); ZST; [&str] } // -> "Foo impl T where U is &str"
spec! { 1u8.foo(1u8); u8; [u8] } // -> "Foo impl T where T is not i32 or ZST"
println!();

// T - Foo4
spec! { s1.foo(); S<i32>; [] } // -> "Foo4 impl S<T> where T is i32"
spec! { x.foo(); Vec<i32>; [] } // -> "Foo4 impl Vec<T> where T is i32"
spec! { s2.foo(); S<u8>; [] } // -> "Default Foo4 for T"
println!();
}
7 changes: 6 additions & 1 deletion spec-trait-impl/crates/spec-trait-utils/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ impl Specializable for ImplBody {
impl ImplBody {
fn get_spec_trait_name(&self) -> String {
match &self.condition {
Some(c) => format!("{}_{}_{}", self.trait_name, self.type_name, to_hash(c)),
Some(c) => format!(
"{}_{}_{}",
self.trait_name,
to_hash(&self.type_name),
to_hash(c)
),
None => self.trait_name.to_owned(),
}
}
Expand Down
194 changes: 192 additions & 2 deletions spec-trait-impl/crates/spec-trait-utils/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::{
use proc_macro2::Span;
use std::collections::{HashMap, HashSet};
use syn::{
Expr, GenericArgument, GenericParam, Generics, Ident, PathArguments, Type, TypeArray,
TypeReference, TypeSlice, TypeTuple,
Expr, GenericArgument, GenericParam, Generics, Ident, PathArguments, ReturnType, Type,
TypeArray, TypeReference, TypeSlice, TypeTuple,
};

pub type Aliases = HashMap<String, Vec<String>>;
Expand Down Expand Up @@ -59,6 +59,22 @@ fn resolve_type(ty: &Type, aliases: &Aliases) -> Type {
})
}

// fn(T) -> U
Type::BareFn(bare_fn) => {
let mut resolved = bare_fn.clone();

for input in &mut resolved.inputs {
input.ty = resolve_type(&input.ty, aliases);
}

if let ReturnType::Type(arrow, ty) = &resolved.output {
let new_ty = resolve_type(ty, aliases);
resolved.output = ReturnType::Type(*arrow, Box::new(new_ty));
}

Type::BareFn(resolved)
}

// T, T<U>
Type::Path(type_path) if type_path.qself.is_none() => {
let mut resolved_path = type_path.clone();
Expand Down Expand Up @@ -205,6 +221,41 @@ fn can_assign(
|| to_string(&array1.len) == to_string(&array2.len))
}

// fn(T) -> U
(Type::BareFn(fn1), Type::BareFn(fn2)) => {
let unsafety_compatible = matches!(
(&fn1.unsafety, &fn2.unsafety),
(None, None) | (Some(_), Some(_))
);

let abi_compatible = match (&fn1.abi, &fn2.abi) {
(None, None) => true,
(Some(a1), Some(a2)) => to_string(a1) == to_string(a2),
_ => false,
};

fn1.inputs.len() == fn2.inputs.len()
&& unsafety_compatible
&& abi_compatible
&& fn1
.inputs
.iter()
.zip(&fn2.inputs)
.all(|(arg1, arg2)| can_assign(&arg1.ty, &arg2.ty, generics))
&& {
let ret1 = match &fn1.output {
ReturnType::Default => str_to_type_name("()"),
ReturnType::Type(_, ty) => *ty.clone(),
};
let ret2 = match &fn2.output {
ReturnType::Default => str_to_type_name("()"),
ReturnType::Type(_, ty) => *ty.clone(),
};

can_assign(&ret1, &ret2, generics)
}
}

// `T`, `T<U>`, `T<_>`
(Type::Path(path1), Type::Path(path2))
if path1.qself.is_none() && path2.qself.is_none() =>
Expand Down Expand Up @@ -379,6 +430,18 @@ pub fn replace_type(ty: &mut Type, prev: &str, new: &Type) {
}
}
}

// fn(T) -> U
Type::BareFn(bare_fn) => {
for input in &mut bare_fn.inputs {
replace_type(&mut input.ty, prev, new);
}

if let ReturnType::Type(_, ty) = &mut bare_fn.output {
replace_type(ty, prev, new);
}
}

_ => {}
}
}
Expand Down Expand Up @@ -411,6 +474,15 @@ pub fn replace_lifetime(ty: &mut Type, prev: &str, new: &str) {
}
}
}
Type::BareFn(bare_fn) => {
for input in &mut bare_fn.inputs {
replace_lifetime(&mut input.ty, prev, new);
}

if let ReturnType::Type(_, ty) = &mut bare_fn.output {
replace_lifetime(ty, prev, new);
}
}
_ => {}
}
}
Expand Down Expand Up @@ -460,6 +532,18 @@ pub fn strip_lifetimes(ty: &mut Type, generics: &Generics) {
}
}
}

// fn(T) -> U
Type::BareFn(bare_fn) => {
for input in &mut bare_fn.inputs {
strip_lifetimes(&mut input.ty, generics);
}

if let ReturnType::Type(_, ty) = &mut bare_fn.output {
strip_lifetimes(ty, generics);
}
}

_ => {}
}
}
Expand Down Expand Up @@ -536,6 +620,20 @@ pub fn assign_lifetimes(t1: &mut Type, t2: &Type, generics: &mut ConstrainedGene
};
}),

// fn(T) -> U
(Type::BareFn(fn1), Type::BareFn(fn2)) => {
fn1.inputs
.iter_mut()
.zip(&fn2.inputs)
.for_each(|(arg1, arg2)| assign_lifetimes(&mut arg1.ty, &arg2.ty, generics));

if let (ReturnType::Type(_, ty1), ReturnType::Type(_, ty2)) =
(&mut fn1.output, &fn2.output)
{
assign_lifetimes(ty1, ty2, generics);
}
}

_ => {}
}
}
Expand Down Expand Up @@ -581,6 +679,17 @@ pub fn replace_infers(
}
}

// fn(T) -> U
Type::BareFn(bare_fn) => {
for input in &mut bare_fn.inputs {
replace_infers(&mut input.ty, generics, counter, new_generics);
}

if let ReturnType::Type(_, ty) = &mut bare_fn.output {
replace_infers(ty, generics, counter, new_generics);
}
}

// _
Type::Infer(_) => {
let name = get_unique_generic_name(generics, counter, None);
Expand Down Expand Up @@ -940,6 +1049,42 @@ mod tests {
assert!(!can_assign(&t1, &t2, &mut g));
}

#[test]
fn compare_types_fn() {
let mut g = ConstrainedGenerics::default();

let t1 = str_to_type_name("fn(u8) -> i32");
let t2 = str_to_type_name("fn(u8) -> i32");
assert!(can_assign(&t1, &t2, &mut g));

let t1 = str_to_type_name("fn(u8) -> i32");
let t2 = str_to_type_name("fn(_) -> _");
assert!(can_assign(&t1, &t2, &mut g));

g.types.insert("T".to_string(), None);
let t1 = str_to_type_name("fn(u8) -> i32");
let t2 = str_to_type_name("fn(T) -> i32");
assert!(can_assign(&t1, &t2, &mut g));

g.types.insert("T".to_string(), None);
let t1 = str_to_type_name("fn(u8) -> i32");
let t2 = str_to_type_name("fn(u8) -> T");
assert!(can_assign(&t1, &t2, &mut g));

g.types.insert("T".to_string(), None);
let t1 = str_to_type_name("fn(u8) -> i32");
let t2 = str_to_type_name("fn(T) -> T");
assert!(!can_assign(&t1, &t2, &mut g));

let t1 = str_to_type_name("fn(u8) -> i32");
let t2 = str_to_type_name("fn(i32) -> i32");
assert!(!can_assign(&t1, &t2, &mut g));

let t1 = str_to_type_name("fn(u8) -> i32");
let t2 = str_to_type_name("fn(u8) -> u8");
assert!(!can_assign(&t1, &t2, &mut g));
}

#[test]
fn compare_types_nested() {
let mut g = ConstrainedGenerics::default();
Expand Down Expand Up @@ -973,6 +1118,7 @@ mod tests {
"(T)",
"Other<T>",
"T<Other>",
"fn(T) -> Other",
];
for ty in types {
let type_ = str_to_type_name(ty);
Expand All @@ -991,6 +1137,7 @@ mod tests {
"(T)",
"Other<T>",
"T<VOther>",
"fn(T) -> Other",
];
for ty in types {
let type_ = str_to_type_name(ty);
Expand Down Expand Up @@ -1116,6 +1263,24 @@ mod tests {
assert_eq!(to_string(&ty).replace(" ", ""), "String".to_string());
}

#[test]
fn replace_type_fn() {
let new_ty: Type = parse2(quote! { String }).unwrap();

let mut ty: Type = parse2(quote! { fn(T) -> T }).unwrap();
replace_type(&mut ty, "T", &new_ty);

assert_eq!(
to_string(&ty).replace(" ", ""),
"fn(String) -> String".to_string().replace(" ", "")
);

let mut ty: Type = parse2(quote! { fn(T) -> T }).unwrap();
replace_type(&mut ty, "fn(T) -> T", &new_ty);

assert_eq!(to_string(&ty).replace(" ", ""), "String".to_string());
}

#[test]
fn replace_type_nested() {
let new_ty: Type = parse2(quote! { String }).unwrap();
Expand Down Expand Up @@ -1271,6 +1436,31 @@ mod tests {
);
}

#[test]
fn replace_infers_fn() {
let mut ty: Type = parse2(quote! { fn(_, &[_]) -> _ }).unwrap();
let mut generics = HashSet::new();
let mut counter = 0;
let mut new_generics = vec![];

replace_infers(&mut ty, &mut generics, &mut counter, &mut new_generics);

assert_eq!(
to_string(&ty).replace(" ", ""),
"fn(__G_0__, &[__G_1__]) -> __G_2__"
.to_string()
.replace(" ", "")
);
assert_eq!(
new_generics,
vec![
"__G_0__".to_string(),
"__G_1__".to_string(),
"__G_2__".to_string()
]
);
}

#[test]
fn strip_lifetimes_simple() {
let mut ty: Type = parse2(quote! { &'a u8 }).unwrap();
Expand Down
Loading
Loading