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
8 changes: 8 additions & 0 deletions compiler/rustc_abi/src/layout/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug + std::fmt::Display {
fn is_tuple(this: TyAndLayout<'a, Self>) -> bool;
fn is_unit(this: TyAndLayout<'a, Self>) -> bool;
fn is_transparent(this: TyAndLayout<'a, Self>) -> bool;
fn is_complex(this: TyAndLayout<'a, Self>) -> bool;
fn is_scalable_vector(this: TyAndLayout<'a, Self>) -> bool;
/// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
fn is_pass_indirectly_in_non_rustic_abis_flag_set(this: TyAndLayout<'a, Self>) -> bool;
Expand Down Expand Up @@ -220,6 +221,13 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
Ty::is_transparent(self)
}

pub fn is_complex<C>(self) -> bool
where
Ty: TyAbiInterface<'a, C>,
{
Ty::is_complex(self)
}

pub fn is_scalable_vector<C>(self) -> bool
where
Ty: TyAbiInterface<'a, C>,
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,12 @@ bitflags! {
/// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
const PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS = 1 << 5;
const IS_SCALABLE = 1 << 6;
const IS_COMPLEX = 1 << 7;
// Any of these flags being set prevent field reordering optimisation.
const FIELD_ORDER_UNOPTIMIZABLE = ReprFlags::IS_C.bits()
| ReprFlags::IS_SIMD.bits()
| ReprFlags::IS_SCALABLE.bits()
| ReprFlags::IS_COMPLEX.bits()
| ReprFlags::IS_LINEAR.bits();
const ABI_UNOPTIMIZABLE = ReprFlags::IS_C.bits() | ReprFlags::IS_SIMD.bits();
}
Expand Down Expand Up @@ -178,6 +180,11 @@ impl ReprOptions {
self.flags.contains(ReprFlags::IS_C)
}

#[inline]
pub fn complex(&self) -> bool {
self.flags.contains(ReprFlags::IS_COMPLEX)
}

#[inline]
pub fn packed(&self) -> bool {
self.pack.is_some()
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,20 @@ fn parse_repr(cx: &mut AcceptContext<'_, '_>, param: &MetaItemParser) -> Option<
cx.expect_no_args(param.args())?;
Some(ReprSimd)
}
Some(sym::complex) => {
if cx.features.is_some_and(|feats| !feats.repr_complex()) {
feature_err(
&cx.sess(),
sym::repr_complex,
param.span(),
"`repr(complex)` is experimental",
)
.emit();
}
cx.check_target("(complex)", &AllowedTargets::AllowList(&[Allow(Target::Struct)]));
cx.expect_no_args(param.args())?;
Some(ReprComplex)
}
Comment thread
mejrs marked this conversation as resolved.
Some(sym::transparent) => {
cx.check_target(
"(transparent)",
Expand Down Expand Up @@ -190,6 +204,7 @@ fn parse_repr(cx: &mut AcceptContext<'_, '_>, param: &MetaItemParser) -> Option<
sym::Rust,
sym::C,
sym::simd,
sym::complex,
sym::transparent,
sym::i8,
sym::u8,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ declare_features! (
(internal, panic_runtime, "1.10.0", Some(32837)),
/// Allows using pattern types.
(internal, pattern_types, "1.79.0", Some(123646)),
/// Allows `repr(complex)` for defining types that are ABI-compatible with C `_Complex`.
(internal, repr_complex, "CURRENT_RUSTC_VERSION", Some(154023)),
/// Allows `repr(simd)` and importing the various simd intrinsics.
(internal, repr_simd, "1.4.0", Some(27731)),
/// Allows using compiler's own crates.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ pub enum ReprAttr {
ReprC,
ReprPacked(Align),
ReprSimd,
ReprComplex,
ReprTransparent,
ReprAlign(Align),
}
Expand Down
42 changes: 42 additions & 0 deletions compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ fn check_struct(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuarante
check_scalable_vector(tcx, span, def_id, scalable);
} else if def.repr().simd() {
check_simd(tcx, span, def_id);
} else if def.repr().complex() {
check_complex(tcx, span, def_id);
}

check_transparent(tcx, def);
Expand Down Expand Up @@ -1527,6 +1529,46 @@ fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
}
}

fn check_complex(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
let t = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
let ty::Adt(def, args) = t.kind() else { return };

// This constraint is enforced during attribute parsing.
assert!(def.is_struct(), "`repr(complex)` is only allowed on structs");

let fields = &def.non_enum_variant().fields;

// A complex number is a pair, so we require exactly two fields of the same type.
let (Some(first), Some(second)) = (fields.get(FieldIdx::ZERO), fields.get(FieldIdx::ONE))
else {
tcx.dcx().struct_span_err(sp, "`repr(complex)` type must have two fields").emit();
return;
};

if let Some(third) = fields.get(FieldIdx::from_usize(2)) {
tcx.dcx()
.struct_span_err(sp, "`repr(complex)` type cannot have more than two fields")
.with_span_label(tcx.def_span(third.did), "excess field")
.emit();
return;
}

// repr(complex) is internal, so this check is not as thorough as we'd require if it were
// user-facing.
Comment on lines +1556 to +1557

@folkertdev folkertdev Jul 7, 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.

Lmk if we should be more thorough here, but given that we never want to stabilize repr(complex), only Complex<T>, this seems (more than) adequate.

View changes since the review

let first_ty = first.ty(tcx, args).skip_norm_wip();
let second_ty = second.ty(tcx, args).skip_norm_wip();
if first_ty != second_ty {
tcx.dcx()
.struct_span_err(sp, "`repr(complex)` type must have two fields of the same type")
.with_span_label(tcx.def_span(first.did), format!("this field is of type `{first_ty}`"))
.with_span_label(
tcx.def_span(second.did),
format!("this field is of type `{second_ty}`"),
)
.emit();
}
}

#[tracing::instrument(skip(tcx), level = "debug")]
fn check_scalable_vector(tcx: TyCtxt<'_>, span: Span, def_id: LocalDefId, scalable: ScalableElt) {
let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
Expand Down
34 changes: 33 additions & 1 deletion compiler/rustc_lint/src/types/improper_ctypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
debug_assert!(matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union));
use FfiResult::*;

if !def.repr().c() && !def.repr().transparent() {
if !def.repr().c() && !def.repr().transparent() && !def.repr().complex() {

@asquared31415 asquared31415 Jul 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This likely needs some work, for example Complex<{integer}> or even weirder types like Complex<String> (if that's even allowed by the compiler) should not be considered FFI safe. Easily done in a followup however.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actually Complex<{integer}> might be de-facto fine under GCC and Clang, I'm not certain. It's a non-standard C extension though, so not sure what we want to do about that.

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.

I recommend we err conservatively and treat it as nonsense for now until anyone asks for it.

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.

Right, takes some more logic but we now only accept (transparently wrapped) float types, anything else triggers improper_ctypes.

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.

btw it is my intention to make Complex<{integer}> just be abi-compatible in practice, but whether we actually guarantee it can be discussed later.

return FfiUnsafe {
ty,
reason: if def.is_struct() {
Expand All @@ -656,6 +656,38 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
};
}

// We only guarantee that Complex<{float}> is C-compatible for now.
if def.repr().complex()
&& let Some(field) = def.non_enum_variant().fields.iter().next()
{
let mut field_ty =
maybe_normalize_erasing_regions(self.cx, field.ty(self.cx.tcx, args));

// Peel off any transparent wrappers.
while let ty::Adt(inner, inner_args) = field_ty.kind()
&& inner.repr().transparent()
&& let Some(inner_field) =
super::transparent_newtype_field(self.cx.tcx, inner.non_enum_variant())
{
field_ty = maybe_normalize_erasing_regions(
self.cx,
inner_field.ty(self.cx.tcx, inner_args),
);
}

if !field_ty.is_floating_point() {
return FfiUnsafe {
ty,
reason: msg!(
"this `repr(complex)` struct only has a C-compatible layout with floating-point fields"
),
help: Some(msg!(
"only `repr(complex)` structs whose fields are `f16`, `f32`, `f64`, or `f128` have a C-compatible layout"
)),
};
}
}

if def.non_enum_variant().field_list_has_applicable_non_exhaustive() {
return FfiUnsafe {
ty,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,10 @@ where
matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().transparent())
}

fn is_complex(this: TyAndLayout<'tcx>) -> bool {
matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().complex())
}

fn is_scalable_vector(this: TyAndLayout<'tcx>) -> bool {
this.ty.is_scalable_vector()
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1600,6 +1600,7 @@ impl<'tcx> TyCtxt<'tcx> {
}
attr::ReprTransparent => ReprFlags::IS_TRANSPARENT,
attr::ReprSimd => ReprFlags::IS_SIMD,
attr::ReprComplex => ReprFlags::IS_COMPLEX,
attr::ReprInt(i) => {
size = Some(match i {
attr::IntType::SignedInt(x) => match x {
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
let mut is_explicit_rust = false;
let mut is_c = false;
let mut is_simd = false;
let mut is_complex = false;
let mut is_transparent = false;

for (repr, _repr_span) in reprs {
Expand All @@ -1232,6 +1233,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
ReprAttr::ReprSimd => {
is_simd = true;
}
ReprAttr::ReprComplex => {
is_complex = true;
}
ReprAttr::ReprTransparent => {
is_transparent = true;
}
Expand Down Expand Up @@ -1264,19 +1268,21 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
target: target.to_string(),
});
}
if is_explicit_rust && (int_reprs > 0 || is_c || is_simd) {
if is_explicit_rust && (int_reprs > 0 || is_c || is_simd || is_complex) {
let hint_spans = hint_spans.clone().collect();
self.dcx().emit_err(diagnostics::ReprConflicting { hint_spans });
}
// Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
if (int_reprs > 1)
|| (is_simd && is_c)
|| (is_complex && is_c)
|| (int_reprs == 1
&& is_c
&& item.is_some_and(|item| {
if let ItemLike::Item(item) = item { is_c_like_enum(item) } else { false }
}))
{
// FIXME: is_complex && is_c should be a hard error.
self.tcx.emit_node_span_lint(
CONFLICTING_REPR_HINTS,
hir_id,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ symbols! {
compiler_copy,
compiler_fence,
compiler_move,
complex,
concat,
concat_bytes,
conservative_impl_trait,
Expand Down Expand Up @@ -1702,6 +1703,7 @@ symbols! {
repr128,
repr_align,
repr_align_enum,
repr_complex,
repr_packed,
repr_simd,
repr_transparent,
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
#![feature(pattern_types)]
#![feature(pin_macro_internals)]
#![feature(prelude_import)]
#![feature(repr_complex)]
#![feature(repr_simd)]
#![feature(rustc_attrs)]
#![feature(rustdoc_internals)]
Expand Down
19 changes: 19 additions & 0 deletions library/core/src/num/complex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// A complex number.
#[derive(Clone, Copy, Debug, PartialEq)]
#[unstable(feature = "complex_numbers", issue = "154023")]
#[repr(complex)]
pub struct Complex<T> {
/// The real component.
pub re: T,
/// The imaginary component.
pub im: T,
}

#[unstable(feature = "complex_numbers", issue = "154023")]
impl<T> Complex<T> {
/// Create a new complex number from a real and imaginary component.
#[must_use]
pub fn new(re: T, im: T) -> Complex<T> {
Complex { re, im }
}
}
3 changes: 3 additions & 0 deletions library/core/src/num/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ mod int_macros; // import int_impl!
#[macro_use]
mod uint_macros; // import uint_impl!

mod complex;
mod error;
#[cfg(not(no_fp_fmt_parse))]
mod float_parse;
Expand All @@ -54,6 +55,8 @@ mod wrapping;
#[doc(hidden)]
pub mod niche_types;

#[unstable(feature = "complex_numbers", issue = "154023")]
pub use complex::Complex;
#[stable(feature = "int_error_matching", since = "1.55.0")]
pub use error::IntErrorKind;
#[stable(feature = "rust1", since = "1.0.0")]
Expand Down
2 changes: 2 additions & 0 deletions library/std/src/num/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#![stable(feature = "rust1", since = "1.0.0")]
#![allow(missing_docs)]

#[unstable(feature = "complex_numbers", issue = "154023")]
pub use core::num::Complex;
#[stable(feature = "int_error_matching", since = "1.55.0")]
pub use core::num::IntErrorKind;
#[stable(feature = "generic_nonzero", since = "1.79.0")]
Expand Down
13 changes: 13 additions & 0 deletions tests/auxiliary/minicore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
f16,
f128,
transparent_unions,
repr_complex,
asm_experimental_arch,
unboxed_closures
)]
Expand Down Expand Up @@ -390,6 +391,18 @@ pub mod hint {
}
}

pub mod num {
use super::Copy;

#[repr(complex)]
pub struct Complex<T> {
pub re: T,
pub im: T,
}

impl<T: Copy> Copy for Complex<T> {}
}

#[lang = "c_void"]
#[repr(u8)]
pub enum c_void {
Expand Down
Loading
Loading