Skip to content

Commit 01e83ad

Browse files
committed
c-variadic: allow trait methods to be c-variadic
but a C-variadic method makes a trait dyn-incompatible. That is because methods from dyn traits, when cast to a function pointer, create a shim. That shim can't really forward the c-variadic arguments.
1 parent fd48528 commit 01e83ad

File tree

13 files changed

+158
-89
lines changed

13 files changed

+158
-89
lines changed

compiler/rustc_ast_passes/messages.ftl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ ast_passes_body_in_extern = incorrect `{$kind}` inside `extern` block
6464
6565
ast_passes_bound_in_context = bounds on `type`s in {$ctx} have no effect
6666
67-
ast_passes_c_variadic_associated_function = associated functions cannot have a C variable argument list
68-
6967
ast_passes_c_variadic_bad_extern = `...` is not supported for `extern "{$abi}"` functions
7068
.label = `extern "{$abi}"` because of this
7169
.help = only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -696,43 +696,36 @@ impl<'a> AstValidator<'a> {
696696

697697
match fn_ctxt {
698698
FnCtxt::Foreign => return,
699-
FnCtxt::Free | FnCtxt::Assoc(AssocCtxt::Impl { of_trait: false }) => {
700-
match sig.header.ext {
701-
Extern::Implicit(_) => {
702-
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
703-
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
704-
span: variadic_param.span,
705-
unsafe_span: sig.safety_span(),
706-
});
707-
}
699+
FnCtxt::Free | FnCtxt::Assoc(_) => match sig.header.ext {
700+
Extern::Implicit(_) => {
701+
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
702+
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
703+
span: variadic_param.span,
704+
unsafe_span: sig.safety_span(),
705+
});
708706
}
709-
Extern::Explicit(StrLit { symbol_unescaped, .. }, _) => {
710-
if !matches!(symbol_unescaped, sym::C | sym::C_dash_unwind) {
711-
self.dcx().emit_err(errors::CVariadicBadExtern {
712-
span: variadic_param.span,
713-
abi: symbol_unescaped,
714-
extern_span: sig.extern_span(),
715-
});
716-
}
717-
718-
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
719-
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
720-
span: variadic_param.span,
721-
unsafe_span: sig.safety_span(),
722-
});
723-
}
707+
}
708+
Extern::Explicit(StrLit { symbol_unescaped, .. }, _) => {
709+
if !matches!(symbol_unescaped, sym::C | sym::C_dash_unwind) {
710+
self.dcx().emit_err(errors::CVariadicBadExtern {
711+
span: variadic_param.span,
712+
abi: symbol_unescaped,
713+
extern_span: sig.extern_span(),
714+
});
724715
}
725-
Extern::None => {
726-
let err = errors::CVariadicNoExtern { span: variadic_param.span };
727-
self.dcx().emit_err(err);
716+
717+
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
718+
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
719+
span: variadic_param.span,
720+
unsafe_span: sig.safety_span(),
721+
});
728722
}
729723
}
730-
}
731-
FnCtxt::Assoc(_) => {
732-
// For now, C variable argument lists are unsupported in associated functions.
733-
let err = errors::CVariadicAssociatedFunction { span: variadic_param.span };
734-
self.dcx().emit_err(err);
735-
}
724+
Extern::None => {
725+
let err = errors::CVariadicNoExtern { span: variadic_param.span };
726+
self.dcx().emit_err(err);
727+
}
728+
},
736729
}
737730
}
738731

compiler/rustc_ast_passes/src/errors.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,6 @@ pub(crate) struct ExternItemAscii {
318318
pub block: Span,
319319
}
320320

321-
#[derive(Diagnostic)]
322-
#[diag(ast_passes_c_variadic_associated_function)]
323-
pub(crate) struct CVariadicAssociatedFunction {
324-
#[primary_span]
325-
pub span: Span,
326-
}
327-
328321
#[derive(Diagnostic)]
329322
#[diag(ast_passes_c_variadic_no_extern)]
330323
#[help]

compiler/rustc_middle/src/traits/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,9 @@ impl DynCompatibilityViolation {
823823
DynCompatibilityViolation::Method(name, MethodViolationCode::AsyncFn, _) => {
824824
format!("method `{name}` is `async`").into()
825825
}
826+
DynCompatibilityViolation::Method(name, MethodViolationCode::CVariadic, _) => {
827+
format!("method `{name}` is C-variadic").into()
828+
}
826829
DynCompatibilityViolation::Method(
827830
name,
828831
MethodViolationCode::WhereClauseReferencesSelf,
@@ -977,6 +980,9 @@ pub enum MethodViolationCode {
977980
/// e.g., `fn foo<A>()`
978981
Generic,
979982

983+
/// e.g., `fn (mut ap: ...)`
984+
CVariadic,
985+
980986
/// the method's receiver (`self` argument) can't be dispatched on
981987
UndispatchableReceiver(Option<Span>),
982988
}

compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ fn virtual_call_violations_for_method<'tcx>(
426426
if let Some(code) = contains_illegal_impl_trait_in_trait(tcx, method.def_id, sig.output()) {
427427
errors.push(code);
428428
}
429+
if sig.skip_binder().c_variadic {
430+
errors.push(MethodViolationCode::CVariadic);
431+
}
429432

430433
// We can't monomorphize things like `fn foo<A>(...)`.
431434
let own_counts = tcx.generics_of(method.def_id).own_counts();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Traits where a method is c-variadic are not dyn compatible.
2+
//
3+
// Creating a function pointer from a method on an `&dyn T` value creates a ReifyShim.
4+
// This shim cannot reliably forward C-variadic arguments. Thus the trait as a whole
5+
// is dyn-incompatible to prevent invalid shims from being created.
6+
#![feature(c_variadic)]
7+
8+
#[repr(transparent)]
9+
struct Struct(u64);
10+
11+
trait Trait {
12+
fn get(&self) -> u64;
13+
14+
unsafe extern "C" fn dyn_method_ref(&self, mut ap: ...) -> u64 {
15+
self.get() + unsafe { ap.arg::<u64>() }
16+
}
17+
}
18+
19+
impl Trait for Struct {
20+
fn get(&self) -> u64 {
21+
self.0
22+
}
23+
}
24+
25+
fn main() {
26+
unsafe {
27+
let dyn_object: &dyn Trait = &Struct(64);
28+
//~^ ERROR the trait `Trait` is not dyn compatible
29+
assert_eq!(dyn_object.dyn_method_ref(100), 164);
30+
assert_eq!(
31+
(Trait::dyn_method_ref as unsafe extern "C" fn(_, ...) -> u64)(dyn_object, 100),
32+
164
33+
);
34+
}
35+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error[E0038]: the trait `Trait` is not dyn compatible
2+
--> $DIR/not-dyn-compatible.rs:27:30
3+
|
4+
LL | let dyn_object: &dyn Trait = &Struct(64);
5+
| ^^^^^ `Trait` is not dyn compatible
6+
|
7+
note: for a trait to be dyn compatible it needs to allow building a vtable
8+
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
9+
--> $DIR/not-dyn-compatible.rs:14:26
10+
|
11+
LL | trait Trait {
12+
| ----- this trait is not dyn compatible...
13+
...
14+
LL | unsafe extern "C" fn dyn_method_ref(&self, mut ap: ...) -> u64 {
15+
| ^^^^^^^^^^^^^^ ...because method `dyn_method_ref` is C-variadic
16+
= help: consider moving `dyn_method_ref` to another trait
17+
= help: only type `Struct` implements `Trait`; consider using it directly instead.
18+
19+
error: aborting due to 1 previous error
20+
21+
For more information about this error, try `rustc --explain E0038`.
Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,73 @@
1-
// For now C-variadic arguments in trait methods are rejected, though we aim to lift this
2-
// restriction in the future. In particular we need to think about the interaction with
3-
// `dyn Trait` and the `ReifyShim`s that it may generate for methods.
1+
//@ run-pass
42
#![feature(c_variadic)]
5-
#![crate_type = "lib"]
6-
struct S;
73

8-
impl S {
4+
#[repr(transparent)]
5+
struct Struct(i32);
6+
7+
impl Struct {
98
unsafe extern "C" fn associated_function(mut ap: ...) -> i32 {
109
unsafe { ap.arg() }
1110
}
1211

1312
unsafe extern "C" fn method(&self, mut ap: ...) -> i32 {
14-
unsafe { ap.arg() }
13+
self.0 + unsafe { ap.arg::<i32>() }
1514
}
1615
}
1716

18-
trait T {
17+
trait Trait: Sized {
18+
fn get(&self) -> i32;
19+
1920
unsafe extern "C" fn trait_associated_function(mut ap: ...) -> i32 {
20-
//~^ ERROR: associated functions cannot have a C variable argument list
2121
unsafe { ap.arg() }
2222
}
2323

24-
unsafe extern "C" fn trait_method(&self, mut ap: ...) -> i32 {
25-
//~^ ERROR: associated functions cannot have a C variable argument list
26-
unsafe { ap.arg() }
24+
unsafe extern "C" fn trait_method_owned(self, mut ap: ...) -> i32 {
25+
self.get() + unsafe { ap.arg::<i32>() }
26+
}
27+
28+
unsafe extern "C" fn trait_method_ref(&self, mut ap: ...) -> i32 {
29+
self.get() + unsafe { ap.arg::<i32>() }
30+
}
31+
32+
unsafe extern "C" fn trait_method_mut(&mut self, mut ap: ...) -> i32 {
33+
self.get() + unsafe { ap.arg::<i32>() }
34+
}
35+
36+
unsafe extern "C" fn trait_fat_pointer(self: Box<Self>, mut ap: ...) -> i32 {
37+
self.get() + unsafe { ap.arg::<i32>() }
2738
}
2839
}
2940

30-
impl T for S {}
41+
impl Trait for Struct {
42+
fn get(&self) -> i32 {
43+
self.0
44+
}
45+
}
3146

3247
fn main() {
3348
unsafe {
34-
assert_eq!(S::associated_function(32), 32);
35-
assert_eq!(S.method(32), 32);
49+
assert_eq!(Struct::associated_function(32), 32);
50+
assert_eq!(Struct(100).method(32), 132);
51+
52+
assert_eq!(Struct::trait_associated_function(32), 32);
53+
assert_eq!(Struct(100).trait_method_owned(32), 132);
54+
assert_eq!(Struct(100).trait_method_ref(32), 132);
55+
assert_eq!(Struct(100).trait_method_mut(32), 132);
56+
assert_eq!(Struct::trait_fat_pointer(Box::new(Struct(100)), 32), 132);
57+
58+
assert_eq!(<Struct as Trait>::trait_associated_function(32), 32);
59+
assert_eq!(Trait::trait_method_owned(Struct(100), 32), 132);
60+
assert_eq!(Trait::trait_method_ref(&Struct(100), 32), 132);
61+
assert_eq!(Trait::trait_method_mut(&mut Struct(100), 32), 132);
62+
assert_eq!(Trait::trait_fat_pointer(Box::new(Struct(100)), 32), 132);
63+
64+
type Associated = unsafe extern "C" fn(...) -> i32;
65+
type Method<T> = unsafe extern "C" fn(T, ...) -> i32;
3666

37-
assert_eq!(S::trait_associated_function(32), 32);
38-
assert_eq!(S.trait_method(32), 32);
67+
assert_eq!((Struct::trait_associated_function as Associated)(32), 32);
68+
assert_eq!((Struct::trait_method_owned as Method<_>)(Struct(100), 32), 132);
69+
assert_eq!((Struct::trait_method_ref as Method<_>)(&Struct(100), 32), 132);
70+
assert_eq!((Struct::trait_method_mut as Method<_>)(&mut Struct(100), 32), 132);
71+
assert_eq!((Struct::trait_fat_pointer as Method<_>)(Box::new(Struct(100)), 32), 132);
3972
}
4073
}

tests/ui/c-variadic/trait-method.stderr

Lines changed: 0 additions & 14 deletions
This file was deleted.

tests/ui/feature-gates/feature-gate-c_variadic.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ pub unsafe extern "C" fn test(_: i32, ap: ...) {}
66
trait Trait {
77
unsafe extern "C" fn trait_test(_: i32, ap: ...) {}
88
//~^ ERROR C-variadic functions are unstable
9-
//~| ERROR associated functions cannot have a C variable argument list
109
}

0 commit comments

Comments
 (0)