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
| 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 @@ -5490,9 +5489,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 @@ -1145,7 +1145,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));
}
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}`
| |
| 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 an `Fn()` closure, found `extern "C" fn() {f}`
}
19 changes: 0 additions & 19 deletions tests/ui/extern/extern-wrong-value-type.stderr

This file was deleted.

12 changes: 3 additions & 9 deletions tests/ui/mismatched_types/suggest-option-asderef-unfixable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ fn produces_string() -> Option<String> {
Some("my cool string".to_owned())
}

fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
fn takes_str_but_too_many_refs(_: &str) -> Option<()> {
Some(())
}

fn no_args() -> Option<()> {
Some(())
}

extern "C" fn takes_str_but_wrong_abi(_: &str) -> Option<()> {
Some(())
}

unsafe fn takes_str_but_unsafe(_: &str) -> Option<()> {
unsafe fn takes_str_but_unsafe(_: String) -> Option<()> {
Some(())
}

Expand All @@ -23,10 +19,8 @@ struct TypeWithoutDeref;
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 an `FnOnce(String)` closure, 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 an `FnOnce(String)` closure, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
//~^ ERROR expected an `FnOnce(String)` closure, found `unsafe fn(String) -> 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
48 changes: 20 additions & 28 deletions tests/ui/mismatched_types/suggest-option-asderef-unfixable.stderr
Original file line number Diff line number Diff line change
@@ -1,50 +1,42 @@
error[E0631]: type mismatch in function arguments
--> $DIR/suggest-option-asderef-unfixable.rs:24:40
--> $DIR/suggest-option-asderef-unfixable.rs:20:40
|
LL | fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
| ------------------------------------------------------ found signature defined here
LL | fn takes_str_but_too_many_refs(_: &str) -> Option<()> {
| ----------------------------------------------------- found signature defined here
...
LL | let _ = produces_string().and_then(takes_str_but_too_many_refs);
| -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected due to this
| |
| required by a bound introduced by this call
|
= note: expected function signature `fn(String) -> _`
found function signature `fn(&&str) -> _`
found function signature `fn(&str) -> _`
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 an `FnOnce(String)` closure, 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(takes_str_but_wrong_abi);
| -------- ^^^^^^^^^^^^^^^^^^^^^^^ expected an `FnOnce(String)` closure, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
| |
| required by a bound introduced by this call
LL | let _ = produces_string().and_then(|arg0: String| takes_str_but_too_many_refs(/* &str */));
| ++++++++++++++ ++++++++++++
help: call `Option::as_deref()` first
|
= 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
LL | let _ = produces_string().as_deref().and_then(takes_str_but_too_many_refs);
| +++++++++++

error[E0277]: expected an `FnOnce(String)` closure, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
--> $DIR/suggest-option-asderef-unfixable.rs:28:40
error[E0277]: expected an `FnOnce(String)` closure, found `unsafe fn(String) -> Option<()> {takes_str_but_unsafe}`
--> $DIR/suggest-option-asderef-unfixable.rs:22: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(String)` is not implemented for fn item `unsafe fn(String) -> 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:24:40
|
LL | fn no_args() -> Option<()> {
| -------------------------- takes 0 arguments
Expand All @@ -58,26 +50,26 @@ 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:26:45
|
LL | fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
| ------------------------------------------------------ found signature defined here
LL | fn takes_str_but_too_many_refs(_: &str) -> Option<()> {
| ----------------------------------------------------- found signature defined here
...
LL | let _ = Some(TypeWithoutDeref).and_then(takes_str_but_too_many_refs);
| -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected due to this
| |
| required by a bound introduced by this call
|
= note: expected function signature `fn(TypeWithoutDeref) -> _`
found function signature `fn(&&str) -> _`
found function signature `fn(&str) -> _`
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 _ = Some(TypeWithoutDeref).and_then(|arg0: TypeWithoutDeref| takes_str_but_too_many_refs(/* &&str */));
| ++++++++++++++++++++++++ +++++++++++++
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 an `Fn()` closure, found `unsafe fn() -> i32`
require_fn(g);
//~^ ERROR: expected an `Fn()` closure, found `extern "C" fn() -> i32 {g}`
require_fn(g as extern "C" fn() -> i32);
//~^ ERROR: expected an `Fn()` closure, found `extern "C" fn() -> i32`
require_fn(h);
//~^ ERROR: expected an `Fn()` closure, 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 an `Fn()` closure, found `extern "C" fn() -> i32 {g}`
--> $DIR/fn-trait.rs:22:16
|
LL | require_fn(g);
| ---------- ^ expected an `Fn()` closure, 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 an `Fn()` closure, found `extern "C" fn() -> i32`
--> $DIR/fn-trait.rs:24:16
|
LL | require_fn(g as extern "C" fn() -> i32);
| ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an `Fn()` closure, 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 an `Fn()` closure, 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`.
Loading
Loading