Skip to content
Open
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
37 changes: 37 additions & 0 deletions compiler/rustc_abi/src/extern_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,43 @@ impl ExternAbi {
| Self::Swift => true,
}
}

/// Returns whether safe functions with this ABI implement the `Fn` traits.
/// `false` only for uncallable or internal ABIs.
pub fn is_fn_trait_compatible(self) -> bool {
Comment thread
Jules-Bertholet marked this conversation as resolved.
match self {
ExternAbi::C { .. }
| ExternAbi::System { .. }
| ExternAbi::Rust
| ExternAbi::RustCold
| ExternAbi::RustPreserveNone
| ExternAbi::RustTail
| ExternAbi::EfiApi
| ExternAbi::Swift
| ExternAbi::Aapcs { .. }
| ExternAbi::CmseNonSecureCall
| ExternAbi::CmseNonSecureEntry
| ExternAbi::Cdecl { .. }
| ExternAbi::Stdcall { .. }
| ExternAbi::Fastcall { .. }
| ExternAbi::Thiscall { .. }
| ExternAbi::Vectorcall { .. }
| ExternAbi::SysV64 { .. }
| ExternAbi::Win64 { .. } => true,
ExternAbi::RustCall

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We might need a comment explaining that RustCall implements the Fn traits, but not this way

Suggested change
ExternAbi::RustCall
// RustCall is callable, but it is used as part of implementing the Fn traits for closures,
// so it can't implement the Fn traits via this specific mechanism.
ExternAbi::RustCall

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is actually no reason why we couldn't make rust-call true here. There just also isn't any reason why we should

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems like something worth saying explicitly?

Suggested change
ExternAbi::RustCall
// RustCall is callable, but it is also used as part of implementing the Fn traits for closures.
// We could make it implement the Fn traits directly, but we have no reason to right now.
ExternAbi::RustCall

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We have a comment at the top of the function saying that we make all internal ABIs false. I think that is enough, "rust-call" is no different from the other two internal ABIs.

| ExternAbi::RustInvalid
| ExternAbi::LlvmIntrinsic
| ExternAbi::Custom
| ExternAbi::GpuKernel
| ExternAbi::PtxKernel
| ExternAbi::AvrInterrupt
| ExternAbi::AvrNonBlockingInterrupt
| ExternAbi::Msp430Interrupt
| ExternAbi::RiscvInterruptM
| ExternAbi::RiscvInterruptS
| ExternAbi::X86Interrupt => false,
}
}
}

pub fn all_names() -> Vec<&'static str> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::path::PathBuf;
use std::{debug_assert_matches, iter};

use itertools::{EitherOrBoth, Itertools};
use rustc_abi::ExternAbi;
use rustc_attr_ir::lang_items::{self, LangItem};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::codes::*;
Expand Down Expand Up @@ -5520,9 +5519,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
..
} = fn_ty.fn_sig(tcx).skip_binder()
// FIXME(splat): this might need to change if the Fn* traits start using/supporting splat
&& fn_sig.abi() == ExternAbi::Rust
&& !fn_sig.c_variadic()
&& fn_sig.safety() == hir::Safety::Safe
&& fn_sig.is_fn_trait_compatible()

// Extract first param of fn sig with peeled refs, e.g. `fn(&T)` -> `T`
&& let Some(&ty::Ref(_, target_ty, needs_mut)) = fn_sig.inputs().first().map(|t| t.kind())
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_type_ir/src/ty_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ impl<I: Interner> FnSig<I> {
}

pub fn is_fn_trait_compatible(self) -> bool {
!self.c_variadic() && self.safety().is_safe() && self.abi() == ExternAbi::Rust
!self.c_variadic() && self.safety().is_safe() && self.abi().is_fn_trait_compatible()

@Jules-Bertholet Jules-Bertholet Jul 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
!self.c_variadic() && self.safety().is_safe() && self.abi().is_fn_trait_compatible()
self.safety().is_safe() && self.abi().is_fn_trait_compatible()

We could try to remove the !self.c_variadic() condition. But there's not much point, as any actual use of variadics is necessarily unsafe. It would also be a bunch of work, as the trait solver currently assumes no type has more than one impl of each Fn trait.

View changes since the review

}

/// Set the safety flag.
Expand Down
39 changes: 39 additions & 0 deletions src/tools/miri/tests/pass/fn-traits-abi.rs
Comment thread
RalfJung marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! Test implementation of `Fn` traits for functions with non-`Rust` ABI

use std::ops::{Fn, FnMut, FnOnce};

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Foo(i32, i32);

extern "C" fn square(foo: Foo) -> Foo {
Foo(foo.0 * foo.1, foo.0 - foo.1)
}

fn call_it<F: ?Sized + Fn(Foo) -> Foo>(f: &F, i: Foo) -> Foo {
f(i)
}

fn call_it_mut<F: ?Sized + FnMut(Foo) -> Foo>(f: &mut F, i: Foo) -> Foo {
f(i)
}

fn call_it_once<F: FnOnce(Foo) -> Foo>(f: F, i: Foo) -> Foo {
f(i)
}

fn main() {
assert_eq!(call_it(&square, Foo(20, 10)), Foo(200, 10));
assert_eq!(call_it_mut(&mut square, Foo(21, 9)), Foo(189, 12));
assert_eq!(call_it_once(square, Foo(18, -3)), Foo(-54, 21));

let mut square_ptr: extern "C" fn(Foo) -> Foo = square;
assert_eq!(call_it(&square_ptr, Foo(30, 11)), Foo(330, 19));
assert_eq!(call_it_mut(&mut square_ptr, Foo(273, -1)), Foo(-273, 274));
assert_eq!(call_it_once(square_ptr, Foo(27, 27)), Foo(729, 0));

let mut square_dyn: Box<dyn Fn(Foo) -> Foo> = Box::new(square);
assert_eq!(call_it(&*square_dyn, Foo(1, 3)), Foo(3, -2));
assert_eq!(call_it_mut(&mut *square_dyn, Foo(5, 25)), Foo(125, -20));
assert_eq!(call_it_once(square_dyn, Foo(9, 2)), Foo(18, 7));
}
21 changes: 21 additions & 0 deletions tests/ui/async-await/async-closures/c-fn-ptr-asyncfn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//@ run-pass
//@ edition: 2021

use std::future::Future;
use std::pin::Pin;

#[expect(improper_ctypes_definitions)]
pub extern "C" fn abi() -> Pin<Box<dyn Future<Output = ()> + 'static>> {
Box::pin(async {})
}

fn test(f: impl AsyncFn()) {
let _ = async {
f().await;
f().await;
};
}

fn main() {
test(abi);
}
2 changes: 1 addition & 1 deletion tests/ui/async-await/async-closures/fn-exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ fn test(f: impl AsyncFn()) {}

fn main() {
test(unsafety); //~ ERROR the trait bound
test(abi); //~ ERROR the trait bound
test(abi);
}
16 changes: 1 addition & 15 deletions tests/ui/async-await/async-closures/fn-exception.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,6 @@ note: required by a bound in `test`
LL | fn test(f: impl AsyncFn()) {}
| ^^^^^^^^^ required by this bound in `test`

error[E0277]: the trait bound `extern "C" fn() -> Pin<Box<(dyn Future<Output = ()> + 'static)>> {abi}: AsyncFn()` is not satisfied
--> $DIR/fn-exception.rs:18:10
|
LL | test(abi);
| ---- ^^^ the trait `AsyncFn()` is not implemented for fn item `extern "C" fn() -> Pin<Box<(dyn Future<Output = ()> + 'static)>> {abi}`
Comment thread
Jules-Bertholet marked this conversation as resolved.
| |
| required by a bound introduced by this call
|
note: required by a bound in `test`
--> $DIR/fn-exception.rs:14:17
|
LL | fn test(f: impl AsyncFn()) {}
| ^^^^^^^^^ required by this bound in `test`

error: aborting due to 2 previous errors
error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
8 changes: 5 additions & 3 deletions tests/ui/extern/extern-wrong-value-type.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//@ run-pass

extern "C" fn f() {
}

fn is_fn<F>(_: F) where F: Fn() {}
fn is_fn<F>(f: F) where F: Fn() {
f();
}

fn main() {
// extern functions are extern "C" fn
let _x: extern "C" fn() = f; // OK
is_fn(f);
//~^ ERROR expected `Fn()` signature, found `extern "C" fn() {f}`
}
19 changes: 0 additions & 19 deletions tests/ui/extern/extern-wrong-value-type.stderr

This file was deleted.

7 changes: 3 additions & 4 deletions tests/ui/mismatched_types/suggest-option-asderef-unfixable.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
fn produces_string() -> Option<String> {
Some("my cool string".to_owned())
fn produces_string() -> Option<&'static str> {
Some("my cool string")
}

fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
Expand All @@ -24,9 +24,8 @@ fn main() {
let _ = produces_string().and_then(takes_str_but_too_many_refs);
//~^ ERROR type mismatch in function arguments
let _ = produces_string().and_then(takes_str_but_wrong_abi);
//~^ ERROR expected `FnOnce(String)` signature, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
let _ = produces_string().and_then(takes_str_but_unsafe);
//~^ ERROR expected `FnOnce(String)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
//~^ ERROR expected `FnOnce(&str)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
let _ = produces_string().and_then(no_args);
//~^ ERROR function is expected to take 1 argument, but it takes 0 arguments
let _ = Some(TypeWithoutDeref).and_then(takes_str_but_too_many_refs);
Expand Down
33 changes: 13 additions & 20 deletions tests/ui/mismatched_types/suggest-option-asderef-unfixable.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,35 @@ LL | let _ = produces_string().and_then(takes_str_but_too_many_refs);
| |
| required by a bound introduced by this call
|
= note: expected function signature `fn(String) -> _`
found function signature `fn(&&str) -> _`
= note: expected function signature `fn(&_) -> _`
found function signature `fn(&&_) -> _`
note: required by a bound in `Option::<T>::and_then`
--> $SRC_DIR/core/src/option.rs:LL:COL
help: consider wrapping the function in a closure
|
LL | let _ = produces_string().and_then(|arg0: String| takes_str_but_too_many_refs(/* &&str */));
| ++++++++++++++ +++++++++++++

error[E0277]: expected `FnOnce(String)` signature, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
--> $DIR/suggest-option-asderef-unfixable.rs:26:40
LL | let _ = produces_string().and_then(|arg0: &str| takes_str_but_too_many_refs(&arg0));
| ++++++++++++ +++++++
help: consider adjusting the signature so it does not borrow its argument
|
LL | let _ = produces_string().and_then(takes_str_but_wrong_abi);
| -------- ^^^^^^^^^^^^^^^^^^^^^^^ expected `FnOnce(String)` signature, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
| |
| required by a bound introduced by this call
LL - fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
LL + fn takes_str_but_too_many_refs(_: &str) -> Option<()> {
|
= help: the trait `FnOnce(String)` is not implemented for fn item `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
note: required by a bound in `Option::<T>::and_then`
--> $SRC_DIR/core/src/option.rs:LL:COL

error[E0277]: expected `FnOnce(String)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
--> $DIR/suggest-option-asderef-unfixable.rs:28:40
error[E0277]: expected `FnOnce(&str)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
--> $DIR/suggest-option-asderef-unfixable.rs:27:40
|
LL | let _ = produces_string().and_then(takes_str_but_unsafe);
| -------- ^^^^^^^^^^^^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }`
| |
| required by a bound introduced by this call
|
= help: the trait `FnOnce(String)` is not implemented for fn item `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
= help: the trait `FnOnce(&str)` is not implemented for fn item `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
= note: unsafe function cannot be called generically without an unsafe block
note: required by a bound in `Option::<T>::and_then`
--> $SRC_DIR/core/src/option.rs:LL:COL

error[E0593]: function is expected to take 1 argument, but it takes 0 arguments
--> $DIR/suggest-option-asderef-unfixable.rs:30:40
--> $DIR/suggest-option-asderef-unfixable.rs:29:40
|
LL | fn no_args() -> Option<()> {
| -------------------------- takes 0 arguments
Expand All @@ -58,7 +51,7 @@ note: required by a bound in `Option::<T>::and_then`
--> $SRC_DIR/core/src/option.rs:LL:COL

error[E0631]: type mismatch in function arguments
--> $DIR/suggest-option-asderef-unfixable.rs:32:45
--> $DIR/suggest-option-asderef-unfixable.rs:31:45
|
LL | fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
| ------------------------------------------------------ found signature defined here
Expand All @@ -77,7 +70,7 @@ help: consider wrapping the function in a closure
LL | let _ = Some(TypeWithoutDeref).and_then(|arg0: TypeWithoutDeref| takes_str_but_too_many_refs(/* &&str */));
| ++++++++++++++++++++++++ +++++++++++++

error: aborting due to 5 previous errors
error: aborting due to 4 previous errors

Some errors have detailed explanations: E0277, E0593, E0631.
For more information about an error, try `rustc --explain E0277`.
2 changes: 0 additions & 2 deletions tests/ui/traits/next-solver/fn-trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ fn main() {
require_fn(f as unsafe fn() -> i32);
//~^ ERROR: expected `Fn()` signature, found `unsafe fn() -> i32`
require_fn(g);
//~^ ERROR: expected `Fn()` signature, found `extern "C" fn() -> i32 {g}`
require_fn(g as extern "C" fn() -> i32);
//~^ ERROR: expected `Fn()` signature, found `extern "C" fn() -> i32`
require_fn(h);
//~^ ERROR: expected `Fn()` signature, found `unsafe fn() -> i32 {h}`
}
36 changes: 2 additions & 34 deletions tests/ui/traits/next-solver/fn-trait.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,8 @@ note: required by a bound in `require_fn`
LL | fn require_fn(_: impl Fn() -> i32) {}
| ^^^^^^^^^^^ required by this bound in `require_fn`

error[E0277]: expected `Fn()` signature, found `extern "C" fn() -> i32 {g}`
--> $DIR/fn-trait.rs:22:16
|
LL | require_fn(g);
| ---------- ^ expected `Fn()` signature, found `extern "C" fn() -> i32 {g}`
| |
| required by a bound introduced by this call
|
= help: the trait `Fn()` is not implemented for fn item `extern "C" fn() -> i32 {g}`
= note: wrap the `extern "C" fn() -> i32 {g}` in a closure with no arguments: `|| { /* code */ }`
note: required by a bound in `require_fn`
--> $DIR/fn-trait.rs:3:23
|
LL | fn require_fn(_: impl Fn() -> i32) {}
| ^^^^^^^^^^^ required by this bound in `require_fn`

error[E0277]: expected `Fn()` signature, found `extern "C" fn() -> i32`
--> $DIR/fn-trait.rs:24:16
|
LL | require_fn(g as extern "C" fn() -> i32);
| ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Fn()` signature, found `extern "C" fn() -> i32`
| |
| required by a bound introduced by this call
|
= help: the trait `Fn()` is not implemented for `extern "C" fn() -> i32`
= note: wrap the `extern "C" fn() -> i32` in a closure with no arguments: `|| { /* code */ }`
note: required by a bound in `require_fn`
--> $DIR/fn-trait.rs:3:23
|
LL | fn require_fn(_: impl Fn() -> i32) {}
| ^^^^^^^^^^^ required by this bound in `require_fn`

error[E0277]: expected `Fn()` signature, found `unsafe fn() -> i32 {h}`
--> $DIR/fn-trait.rs:26:16
--> $DIR/fn-trait.rs:24:16
|
LL | require_fn(h);
| ---------- ^ call the function in a closure: `|| unsafe { /* code */ }`
Expand All @@ -64,6 +32,6 @@ note: required by a bound in `require_fn`
LL | fn require_fn(_: impl Fn() -> i32) {}
| ^^^^^^^^^^^ required by this bound in `require_fn`

error: aborting due to 4 previous errors
error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
40 changes: 40 additions & 0 deletions tests/ui/unboxed-closures/unboxed-closures-abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//@ run-pass
//! Test implementation of `Fn` traits for functions with non-`Rust` ABI

use std::ops::{Fn, FnMut, FnOnce};

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Foo(i32, i32);

extern "C" fn square(foo: Foo) -> Foo {
Foo(foo.0 * foo.1, foo.0 - foo.1)
}

fn call_it<F: ?Sized + Fn(Foo) -> Foo>(f: &F, i: Foo) -> Foo {
f(i)
}

fn call_it_mut<F: ?Sized + FnMut(Foo) -> Foo>(f: &mut F, i: Foo) -> Foo {
f(i)
}

fn call_it_once<F: FnOnce(Foo) -> Foo>(f: F, i: Foo) -> Foo {
f(i)
}

fn main() {
assert_eq!(call_it(&square, Foo(20, 10)), Foo(200, 10));
assert_eq!(call_it_mut(&mut square, Foo(21, 9)), Foo(189, 12));
assert_eq!(call_it_once(square, Foo(18, -3)), Foo(-54, 21));

let mut square_ptr: extern "C" fn(Foo) -> Foo = square;
assert_eq!(call_it(&square_ptr, Foo(30, 11)), Foo(330, 19));
assert_eq!(call_it_mut(&mut square_ptr, Foo(273, -1)), Foo(-273, 274));
assert_eq!(call_it_once(square_ptr, Foo(27, 27)), Foo(729, 0));

let mut square_dyn: Box<dyn Fn(Foo) -> Foo> = Box::new(square);
assert_eq!(call_it(&*square_dyn, Foo(1, 3)), Foo(3, -2));
assert_eq!(call_it_mut(&mut *square_dyn, Foo(5, 25)), Foo(125, -20));
assert_eq!(call_it_once(square_dyn, Foo(9, 2)), Foo(18, 7));
}
Loading
Loading