From 58374d7a9ed625b1b2e92f29fd293e7215819dc7 Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:07:18 +0900 Subject: [PATCH 01/10] Modify the error message shown when a user tries to import inherent associated items --- compiler/rustc_resolve/src/ident.rs | 60 ++++++++++++++++++++------- compiler/rustc_resolve/src/imports.rs | 4 +- compiler/rustc_resolve/src/late.rs | 1 + compiler/rustc_resolve/src/lib.rs | 15 +++++-- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 7cfd5b5f861a4..30ed9cf1f54fe 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1823,6 +1823,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { "too many leading `super` keywords".to_string(), "there are too many leading `super` keywords".to_string(), None, + None, ) }, ); @@ -1889,7 +1890,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { "can only be used in path start position".to_string(), ) }; - (message, label, None) + (message, label, None, None) }, ); } @@ -2000,10 +2001,28 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module_had_parse_errors, module, || { + let import_inherent_item_error_flag = + self.tcx.features().import_trait_associated_functions() + && matches!( + res, + Res::Def( + DefKind::Struct + | DefKind::Enum + | DefKind::Union + | DefKind::ForeignTy, + _ + ) + ); + // Show a different error message for items that can have associated items. let label = format!( - "`{ident}` is {} {}, not a module", + "`{ident}` is {} {}, not a module{}", res.article(), - res.descr() + res.descr(), + if import_inherent_item_error_flag { + " or a trait" + } else { + "" + } ); let scope = match &path[..segment_idx] { [.., prev] => { @@ -2018,7 +2037,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // FIXME: reword, as the reason we expected a module is because of // the following path segment. let message = format!("cannot find module `{ident}` in {scope}"); - (message, label, None) + let note = if import_inherent_item_error_flag { + Some( + "cannot import inherent associated items, only trait associated items".to_string(), + ) + } else { + None + }; + (message, label, None, note) }, ); } @@ -2043,18 +2069,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module_had_parse_errors, module, || { - this.get_mut().report_path_resolution_error( - path, - opt_ns, - parent_scope, - ribs, - ignore_decl, - ignore_import, - module, - segment_idx, - ident, - diag_metadata, - ) + let (message, label, suggestion) = + this.get_mut().report_path_resolution_error( + path, + opt_ns, + parent_scope, + ribs, + ignore_decl, + ignore_import, + module, + segment_idx, + ident, + diag_metadata, + ); + (message, label, suggestion, None) }, ); } diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 704c316bce650..f4b0e37b3c30b 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1046,6 +1046,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module, error_implied_by_parse_error: _, message, + note: _, } => { if no_ambiguity { assert!(import.imported_module.get().is_none()); @@ -1069,6 +1070,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { suggestion, module, segment_name, + note, .. } => { if no_ambiguity { @@ -1097,7 +1099,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None => UnresolvedImportError { span, label: Some(label), - note: None, + note, suggestion, candidates: None, segment: Some(segment_name), diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index f72abaa37201e..3dee227b97986 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -4891,6 +4891,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { segment_name, error_implied_by_parse_error: _, message, + note: _, } => { return Err(respan( span, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index b88227264537f..5f2d4e2370839 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -478,6 +478,7 @@ enum PathResult<'ra> { segment_name: Symbol, error_implied_by_parse_error: bool, message: String, + note: Option, }, } @@ -488,13 +489,18 @@ impl<'ra> PathResult<'ra> { finalize: bool, error_implied_by_parse_error: bool, module: Option>, - label_and_suggestion: impl FnOnce() -> (String, String, Option), + label_and_suggestion_and_note: impl FnOnce() -> ( + String, + String, + Option, + Option, + ), ) -> PathResult<'ra> { - let (message, label, suggestion) = if finalize { - label_and_suggestion() + let (message, label, suggestion, note) = if finalize { + label_and_suggestion_and_note() } else { // FIXME: this output isn't actually present in the test suite. - (format!("cannot find `{ident}` in this scope"), String::new(), None) + (format!("cannot find `{ident}` in this scope"), String::new(), None, None) }; PathResult::Failed { span: ident.span, @@ -505,6 +511,7 @@ impl<'ra> PathResult<'ra> { module, error_implied_by_parse_error, message, + note, } } } From 4d5ce5ddab2780f0dc38a450f91e12b309b230c6 Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:08:21 +0900 Subject: [PATCH 02/10] Add note for enum import errors when `import_trait_associated_functions` is enabled --- compiler/rustc_resolve/src/imports.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index f4b0e37b3c30b..0c12fc8c39f01 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1327,6 +1327,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { _ => (lev_suggestion, None), }; + // If importing of trait asscoiated items is enabled, an also find an + // `Enum`, then note that inherent associated items cannot be imported. + let note = if self.tcx.features().import_trait_associated_functions() + && let PathResult::Module(ModuleOrUniformRoot::Module(m)) = path_res + && let Some(Res::Def(DefKind::Enum, _)) = m.res() + { + note.or(Some( + "cannot import inherent associated items, only trait associated items" + .to_string(), + )) + } else { + note + }; + let label = match module { ModuleOrUniformRoot::Module(module) => { let module_str = module_to_string(module); From fae6a311e7cbcc8cb393e7a37f7f57c616e4dd2d Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:09:33 +0900 Subject: [PATCH 03/10] Add UI tests for inherent associated item import attempts --- tests/ui/imports/import-inherent-148009.rs | 68 +++++++++++++++++++ .../ui/imports/import-inherent-148009.stderr | 37 ++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/ui/imports/import-inherent-148009.rs create mode 100644 tests/ui/imports/import-inherent-148009.stderr diff --git a/tests/ui/imports/import-inherent-148009.rs b/tests/ui/imports/import-inherent-148009.rs new file mode 100644 index 0000000000000..81d176ca182c5 --- /dev/null +++ b/tests/ui/imports/import-inherent-148009.rs @@ -0,0 +1,68 @@ +//! Check that when the feature `import_trait_associated_functions` is enabled, +//! and one trys to import inherent associated items, the error message is +//! updated to reflect that only trait associated items can be imported. +//! +//! Regression test for . + +//@ check-fail + +#![feature(import_trait_associated_functions, extern_types)] + +pub struct TestStruct; + +impl TestStruct { + pub fn m1() {} + pub const C1: usize = 0; +} + +pub use self::TestStruct::{C1, m1}; +//~^ ERROR unresolved import `self::TestStruct` [E0432] +//~| NOTE `TestStruct` is a struct, not a module or a trait +//~| NOTE cannot import inherent associated items, only trait associated items + +pub union TestUnion { + pub f: f32, + pub i: i32, +} + +impl TestUnion { + pub fn m2() {} + pub const C2: usize = 0; +} + +pub use self::TestUnion::{C2, m2}; +//~^ ERROR unresolved import `self::TestUnion` [E0432] +//~| NOTE `TestUnion` is a union, not a module or a trait +//~| NOTE cannot import inherent associated items, only trait associated items + +pub enum TestEnum { + V1, + V2, +} + +impl TestEnum { + pub fn m3() {} + pub const C3: usize = 0; +} + +pub use self::TestEnum::{C3, m3}; +//~^ ERROR unresolved imports `self::TestEnum::C3`, `self::TestEnum::m3` [E0432] +//~| NOTE no `m3` in `TestEnum` +//~| NOTE no `C3` in `TestEnum` +//~| NOTE cannot import inherent associated items, only trait associated items + +extern "C" { + pub type TestForeignTy; +} + +impl TestForeignTy { + pub fn m4() {} + pub const C4: usize = 0; +} + +pub use self::TestForeignTy::{C4, m4}; +//~^ ERROR unresolved import `self::TestForeignTy` [E0432] +//~| NOTE `TestForeignTy` is a foreign type, not a module or a trait +//~| NOTE cannot import inherent associated items, only trait associated items + +fn main() {} diff --git a/tests/ui/imports/import-inherent-148009.stderr b/tests/ui/imports/import-inherent-148009.stderr new file mode 100644 index 0000000000000..3265a0f54b65c --- /dev/null +++ b/tests/ui/imports/import-inherent-148009.stderr @@ -0,0 +1,37 @@ +error[E0432]: unresolved import `self::TestStruct` + --> $DIR/import-inherent-148009.rs:18:15 + | +LL | pub use self::TestStruct::{C1, m1}; + | ^^^^^^^^^^ `TestStruct` is a struct, not a module or a trait + | + = note: cannot import inherent associated items, only trait associated items + +error[E0432]: unresolved import `self::TestUnion` + --> $DIR/import-inherent-148009.rs:33:15 + | +LL | pub use self::TestUnion::{C2, m2}; + | ^^^^^^^^^ `TestUnion` is a union, not a module or a trait + | + = note: cannot import inherent associated items, only trait associated items + +error[E0432]: unresolved imports `self::TestEnum::C3`, `self::TestEnum::m3` + --> $DIR/import-inherent-148009.rs:48:26 + | +LL | pub use self::TestEnum::{C3, m3}; + | ^^ ^^ no `m3` in `TestEnum` + | | + | no `C3` in `TestEnum` + | + = note: cannot import inherent associated items, only trait associated items + +error[E0432]: unresolved import `self::TestForeignTy` + --> $DIR/import-inherent-148009.rs:63:15 + | +LL | pub use self::TestForeignTy::{C4, m4}; + | ^^^^^^^^^^^^^ `TestForeignTy` is a foreign type, not a module or a trait + | + = note: cannot import inherent associated items, only trait associated items + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0432`. From 25a84cfd6d0015fea8d8ef3e2c01817aa36fd356 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:50:38 +0000 Subject: [PATCH 04/10] Store a PathBuf rather than SerializedModule for cached modules In cg_gcc ModuleBuffer already only contains a path anyway. And for moving LTO into -Zlink-only we will need to serialize MaybeLtoModules. By storing a path cached modules we avoid writing them to the disk a second time during serialization of MaybeLtoModules. --- compiler/rustc_codegen_gcc/src/back/lto.rs | 7 ++- compiler/rustc_codegen_llvm/src/back/lto.rs | 11 +++-- compiler/rustc_codegen_ssa/src/back/lto.rs | 15 ++++++ compiler/rustc_codegen_ssa/src/back/write.rs | 48 ++++++++------------ 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/back/lto.rs b/compiler/rustc_codegen_gcc/src/back/lto.rs index 401d4c244d5a0..6fd9cc96328ff 100644 --- a/compiler/rustc_codegen_gcc/src/back/lto.rs +++ b/compiler/rustc_codegen_gcc/src/back/lto.rs @@ -144,9 +144,12 @@ fn fat_lto( for module in modules { match module { FatLtoInput::InMemory(m) => in_memory.push(m), - FatLtoInput::Serialized { name, buffer } => { + FatLtoInput::Serialized { name, bitcode_path } => { info!("pushing serialized module {:?}", name); - serialized_modules.push((buffer, CString::new(name).unwrap())); + serialized_modules.push(( + SerializedModule::from_file(&bitcode_path), + CString::new(name).unwrap(), + )); } } } diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index 09863961c9d69..63c09fe940279 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -223,9 +223,12 @@ fn fat_lto( for module in modules { match module { FatLtoInput::InMemory(m) => in_memory.push(m), - FatLtoInput::Serialized { name, buffer } => { + FatLtoInput::Serialized { name, bitcode_path } => { info!("pushing serialized module {:?}", name); - serialized_modules.push((buffer, CString::new(name).unwrap())); + serialized_modules.push(( + SerializedModule::from_file(&bitcode_path), + CString::new(name).unwrap(), + )); } } } @@ -396,7 +399,9 @@ fn thin_lto( for (i, module) in modules.into_iter().enumerate() { let (name, buffer) = match module { ThinLtoInput::Red { name, buffer } => (name, buffer), - ThinLtoInput::Green { wp, buffer } => (wp.cgu_name, buffer), + ThinLtoInput::Green { wp, bitcode_path } => { + (wp.cgu_name, SerializedModule::from_file(&bitcode_path)) + } }; info!("local module: {} - {}", i, name); let cname = CString::new(name.as_bytes()).unwrap(); diff --git a/compiler/rustc_codegen_ssa/src/back/lto.rs b/compiler/rustc_codegen_ssa/src/back/lto.rs index cd380abb75e0e..ddfcd8a85f6b8 100644 --- a/compiler/rustc_codegen_ssa/src/back/lto.rs +++ b/compiler/rustc_codegen_ssa/src/back/lto.rs @@ -1,4 +1,6 @@ use std::ffi::CString; +use std::fs; +use std::path::Path; use std::sync::Arc; use rustc_data_structures::memmap::Mmap; @@ -49,6 +51,19 @@ pub enum SerializedModule { } impl SerializedModule { + pub fn from_file(bc_path: &Path) -> Self { + let file = fs::File::open(&bc_path).unwrap_or_else(|e| { + panic!("failed to open LTO bitcode file `{}`: {}", bc_path.display(), e) + }); + + let mmap = unsafe { + Mmap::map(file).unwrap_or_else(|e| { + panic!("failed to mmap LTO bitcode file `{}`: {}", bc_path.display(), e) + }) + }; + SerializedModule::FromUncompressedFile(mmap) + } + pub fn data(&self) -> &[u8] { match *self { SerializedModule::Local(ref m) => m.data(), diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 82d7e23602e25..72a96e67f6e6f 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -8,7 +8,6 @@ use std::{assert_matches, fs, io, mem, str, thread}; use rustc_abi::Size; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::jobserver::{self, Acquired}; -use rustc_data_structures::memmap::Mmap; use rustc_data_structures::profiling::{SelfProfilerRef, VerboseTimingGuard}; use rustc_errors::emitter::Emitter; use rustc_errors::{ @@ -35,9 +34,8 @@ use rustc_span::{FileName, InnerSpan, Span, SpanData}; use rustc_target::spec::{MergeFunctions, SanitizerSet}; use tracing::debug; -use super::link::{self, ensure_removed}; -use super::lto::{self, SerializedModule}; -use crate::back::lto::check_lto_allowed; +use crate::back::link::{self, ensure_removed}; +use crate::back::lto::{self, SerializedModule, check_lto_allowed}; use crate::errors::ErrorCreatingRemarkDir; use crate::traits::*; use crate::{ @@ -774,13 +772,13 @@ pub(crate) enum WorkItemResult { } pub enum FatLtoInput { - Serialized { name: String, buffer: SerializedModule }, + Serialized { name: String, bitcode_path: PathBuf }, InMemory(ModuleCodegen), } pub enum ThinLtoInput { Red { name: String, buffer: SerializedModule }, - Green { wp: WorkProduct, buffer: SerializedModule }, + Green { wp: WorkProduct, bitcode_path: PathBuf }, } /// Actual LTO type we end up choosing based on multiple factors. @@ -866,7 +864,7 @@ fn execute_optimize_work_item( }); WorkItemResult::NeedsFatLto(FatLtoInput::Serialized { name: module.name, - buffer: SerializedModule::Local(buffer), + bitcode_path: path, }) } None => WorkItemResult::NeedsFatLto(FatLtoInput::InMemory(module)), @@ -1166,10 +1164,7 @@ pub(crate) enum Message { /// Similar to `CodegenDone`, but for reusing a pre-LTO artifact /// Sent from the main thread. - AddImportOnlyModule { - module_data: SerializedModule, - work_product: WorkProduct, - }, + AddImportOnlyModule { bitcode_path: PathBuf, work_product: WorkProduct }, /// The frontend has finished generating everything for all codegen units. /// Sent from the main thread. @@ -1729,10 +1724,10 @@ fn start_executing_work( } } - Message::AddImportOnlyModule { module_data, work_product } => { + Message::AddImportOnlyModule { bitcode_path, work_product } => { assert_eq!(codegen_state, Ongoing); assert_eq!(main_thread_state, MainThreadState::Codegenning); - lto_import_only_modules.push((module_data, work_product)); + lto_import_only_modules.push((bitcode_path, work_product)); main_thread_state = MainThreadState::Idle; } } @@ -1758,8 +1753,8 @@ fn start_executing_work( needs_fat_lto.push(FatLtoInput::InMemory(allocator_module)); } - for (module, wp) in lto_import_only_modules { - needs_fat_lto.push(FatLtoInput::Serialized { name: wp.cgu_name, buffer: module }) + for (bitcode_path, wp) in lto_import_only_modules { + needs_fat_lto.push(FatLtoInput::Serialized { name: wp.cgu_name, bitcode_path }) } return Ok(MaybeLtoModules::FatLto { @@ -1772,8 +1767,8 @@ fn start_executing_work( assert!(compiled_modules.is_empty()); assert!(needs_fat_lto.is_empty()); - for (buffer, wp) in lto_import_only_modules { - needs_thin_lto.push(ThinLtoInput::Green { wp, buffer }) + for (bitcode_path, wp) in lto_import_only_modules { + needs_thin_lto.push(ThinLtoInput::Green { wp, bitcode_path }) } if cgcx.lto == Lto::ThinLocal { @@ -2285,20 +2280,13 @@ pub(crate) fn submit_pre_lto_module_to_llvm( module: CachedModuleCodegen, ) { let filename = pre_lto_bitcode_filename(&module.name); - let bc_path = in_incr_comp_dir_sess(tcx.sess, &filename); - let file = fs::File::open(&bc_path) - .unwrap_or_else(|e| panic!("failed to open bitcode file `{}`: {}", bc_path.display(), e)); - - let mmap = unsafe { - Mmap::map(file).unwrap_or_else(|e| { - panic!("failed to mmap bitcode file `{}`: {}", bc_path.display(), e) - }) - }; + let bitcode_path = in_incr_comp_dir_sess(tcx.sess, &filename); // Schedule the module to be loaded - drop(coordinator.sender.send(Message::AddImportOnlyModule:: { - module_data: SerializedModule::FromUncompressedFile(mmap), - work_product: module.source, - })); + drop( + coordinator + .sender + .send(Message::AddImportOnlyModule:: { bitcode_path, work_product: module.source }), + ); } fn pre_lto_bitcode_filename(module_name: &str) -> String { From 9e4bb8490455c2b7da3af118b191c141f5822022 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:58:59 +0000 Subject: [PATCH 05/10] Reduce visibility of OngoingCodegen fields --- compiler/rustc_codegen_ssa/src/back/write.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 72a96e67f6e6f..5601a950fbdb5 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -2128,14 +2128,14 @@ impl Drop for Coordinator { } pub struct OngoingCodegen { - pub backend: B, - pub output_filenames: Arc, + backend: B, + output_filenames: Arc, // Field order below is intended to terminate the coordinator thread before two fields below // drop and prematurely close channels used by coordinator thread. See `Coordinator`'s // `Drop` implementation for more info. - pub coordinator: Coordinator, - pub codegen_worker_receive: Receiver, - pub shared_emitter_main: SharedEmitterMain, + pub(crate) coordinator: Coordinator, + codegen_worker_receive: Receiver, + shared_emitter_main: SharedEmitterMain, } impl OngoingCodegen { From 440ed80b316ca20841e8ce6172f3635be60c4a35 Mon Sep 17 00:00:00 2001 From: Nivekithan Date: Mon, 20 Apr 2026 18:07:13 +0530 Subject: [PATCH 06/10] add warning message when using x fix --- src/bootstrap/src/core/config/config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 04f020e44dd12..54ec40a3314cd 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -1184,6 +1184,12 @@ impl Config { exit!(1); } + if matches!(flags_cmd, Subcommand::Fix) { + eprintln!( + "WARNING: `x fix` is provided on a best-effort basis and does not support all `cargo fix` options correctly." + ); + } + // CI should always run stage 2 builds, unless it specifically states otherwise #[cfg(not(test))] if flags_stage.is_none() && ci_env.is_running_in_ci() { From 05d6cdb52c267e044a226f89322d1f127ec9a61a Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sat, 24 Jan 2026 13:37:53 +0100 Subject: [PATCH 07/10] ImproperCTypes: move ADT logic into separate visit_ methods Another interal change that shouldn't impact rustc users. The goal (of this commit and a few afterwards) is to break apart the gigantic visit_type function into more managable and easily-editable bits that focus on specific parts of FFI safety. For now, we break the code specific to enums one side, and structs/unions on the other, into separate visit_? methods --- .../rustc_lint/src/types/improper_ctypes.rs | 209 ++++++++++-------- 1 file changed, 115 insertions(+), 94 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 965cd23c762ae..5be1ed1b000ee 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -190,6 +190,7 @@ fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { fn check_arg_for_power_alignment<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { let tcx = cx.tcx; assert!(tcx.sess.target.os == Os::Aix); + // Structs (under repr(C)) follow the power alignment rule if: // - the first field of the struct is a floating-point type that // is greater than 4-bytes, or @@ -377,15 +378,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } /// Checks if the given `VariantDef`'s field types are "ffi-safe". - fn check_variant_for_ffi( + fn visit_variant_fields( &mut self, state: VisitorState, ty: Ty<'tcx>, - def: ty::AdtDef<'tcx>, + def: AdtDef<'tcx>, variant: &ty::VariantDef, args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { use FfiResult::*; + let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. @@ -431,6 +433,115 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } + fn visit_struct_or_union( + &mut self, + state: VisitorState, + ty: Ty<'tcx>, + def: AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!(matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)); + use FfiResult::*; + + if !def.repr().c() && !def.repr().transparent() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + msg!("this struct has unspecified layout") + } else { + msg!("this union has unspecified layout") + }, + help: if def.is_struct() { + Some(msg!( + "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct" + )) + } else { + // FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises + Some(msg!( + "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union" + )) + }, + }; + } + + if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + msg!("this struct is non-exhaustive") + } else { + msg!("this union is non-exhaustive") + }, + help: None, + }; + } + + if def.non_enum_variant().fields.is_empty() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + msg!("this struct has no fields") + } else { + msg!("this union has no fields") + }, + help: if def.is_struct() { + Some(msg!("consider adding a member to this struct")) + } else { + Some(msg!("consider adding a member to this union")) + }, + }; + } + self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) + } + + fn visit_enum( + &mut self, + state: VisitorState, + ty: Ty<'tcx>, + def: AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!(matches!(def.adt_kind(), AdtKind::Enum)); + use FfiResult::*; + + if def.variants().is_empty() { + // Empty enums are okay... although sort of useless. + return FfiSafe; + } + // Check for a repr() attribute to specify the size of the discriminant. + if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { + // Special-case types like `Option` and `Result` + if let Some(ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { + return self.visit_type(state, ty); + } + + return FfiUnsafe { + ty, + reason: msg!("enum has no representation hint"), + help: Some(msg!( + "consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum" + )), + }; + } + + let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); + // Check the contained variants. + let ret = def.variants().iter().try_for_each(|variant| { + check_non_exhaustive_variant(non_exhaustive, variant) + .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; + + match self.visit_variant_fields(state, ty, def, variant, args) { + FfiSafe => ControlFlow::Continue(()), + r => ControlFlow::Break(r), + } + }); + if let ControlFlow::Break(result) = ret { + return result; + } + + FfiSafe + } + /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). fn visit_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { @@ -483,99 +594,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { )), }; } - - if !def.repr().c() && !def.repr().transparent() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - msg!("this struct has unspecified layout") - } else { - msg!("this union has unspecified layout") - }, - help: if def.is_struct() { - Some(msg!( - "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct" - )) - } else { - Some(msg!( - "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union" - )) - }, - }; - } - - if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - msg!("this struct is non-exhaustive") - } else { - msg!("this union is non-exhaustive") - }, - help: None, - }; - } - - if def.non_enum_variant().fields.is_empty() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - msg!("this struct has no fields") - } else { - msg!("this union has no fields") - }, - help: if def.is_struct() { - Some(msg!("consider adding a member to this struct")) - } else { - Some(msg!("consider adding a member to this union")) - }, - }; - } - - self.check_variant_for_ffi(state, ty, def, def.non_enum_variant(), args) - } - AdtKind::Enum => { - if def.variants().is_empty() { - // Empty enums are okay... although sort of useless. - return FfiSafe; - } - // Check for a repr() attribute to specify the size of the - // discriminant. - if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() - { - // Special-case types like `Option` and `Result` - if let Some(ty) = - repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) - { - return self.visit_type(state, ty); - } - - return FfiUnsafe { - ty, - reason: msg!("enum has no representation hint"), - help: Some(msg!( - "consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum" - )), - }; - } - - let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); - // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_exhaustive, variant) - .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; - - match self.check_variant_for_ffi(state, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } - }); - if let ControlFlow::Break(result) = ret { - return result; - } - - FfiSafe + self.visit_struct_or_union(state, ty, def, args) } + AdtKind::Enum => self.visit_enum(state, ty, def, args), } } From d75c93dad24c17ce384e55d7824c9f4d0b2f158a Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sat, 24 Jan 2026 13:37:53 +0100 Subject: [PATCH 08/10] ImproperCTypes: reorder simple type checks Another interal change that shouldn't impact rustc users. the list of simpler type-based decisions made by `visit_type` are reordered and are given better documentation. --- .../rustc_lint/src/types/improper_ctypes.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 5be1ed1b000ee..54dd54451d847 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -600,19 +600,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - ty::Char => FfiUnsafe { + // Pattern types are just extra invariants on the type that you need to uphold, + // but only the base type is relevant for being representable in FFI. + // (note: this lint was written when pattern types could only be integers constrained to ranges) + ty::Pat(pat_ty, _) => self.visit_type(state, pat_ty), + + // types which likely have a stable representation, if the target architecture defines those + // note: before rust 1.77, 128-bit ints were not FFI-safe on x86_64 + ty::Int(..) | ty::Uint(..) | ty::Float(..) => FfiResult::FfiSafe, + + ty::Bool => FfiResult::FfiSafe, + + ty::Char => FfiResult::FfiUnsafe { ty, reason: msg!("the `char` type has no C equivalent"), help: Some(msg!("consider using `u32` or `libc::wchar_t` instead")), }, - // It's just extra invariants on the type that you need to uphold, - // but only the base type is relevant for being representable in FFI. - ty::Pat(base, ..) => self.visit_type(state, base), - - // Primitive types with a stable representation. - ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, - ty::Slice(_) => FfiUnsafe { ty, reason: msg!("slices have no C equivalent"), @@ -687,6 +691,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Foreign(..) => FfiSafe, + ty::Never => FfiSafe, + // While opaque types are checked for earlier, if a projection in a struct field // normalizes to an opaque type, then it will reach this branch. ty::Alias(ty::AliasTy { kind: ty::Opaque { .. }, .. }) => { From 6dfa52ca93fb132e039f242801d9f3cab8c01863 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sat, 24 Jan 2026 13:37:53 +0100 Subject: [PATCH 09/10] ImproperCTypes: regroup Box/Ptr/Ref logic Another interal change that shouldn't impact rustc users. This time, regroup into `visit_indirections` the code dealing with the FFI safety of Boxes, Refs and RawPtrs. --- .../rustc_lint/src/types/improper_ctypes.rs | 125 +++++++++++++----- 1 file changed, 95 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 54dd54451d847..123b62cfdfe94 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -272,6 +272,17 @@ enum FfiResult<'tcx> { /// in the `FfiResult` is final. type PartialFfiResult<'tcx> = Option>; +/// What type indirection points to a given type. +#[derive(Clone, Copy)] +enum IndirectionKind { + /// Box (valid non-null pointer, owns pointee). + Box, + /// Ref (valid non-null pointer, borrows pointee). + Ref, + /// Raw pointer (not necessarily non-null or valid. no info on ownership). + RawPtr, +} + bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct VisitorState: u8 { @@ -377,6 +388,78 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } } + /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". + fn visit_indirection( + &mut self, + state: VisitorState, + ty: Ty<'tcx>, + inner_ty: Ty<'tcx>, + indirection_kind: IndirectionKind, + ) -> FfiResult<'tcx> { + use FfiResult::*; + let tcx = self.cx.tcx; + + match indirection_kind { + IndirectionKind::Box => { + // FIXME(ctypes): this logic is broken, but it still fits the current tests: + // - for some reason `Box<_>`es in `extern "ABI" {}` blocks + // (including within FnPtr:s) + // are not treated as pointers but as FFI-unsafe structs + // - otherwise, treat the box itself correctly, and follow pointee safety logic + // as described in the other `indirection_type` match branch. + if state.is_in_defined_function() + || (state.is_in_fnptr() && matches!(self.base_fn_mode, CItemKind::Definition)) + { + if inner_ty.is_sized(tcx, self.cx.typing_env()) { + return FfiSafe; + } else { + return FfiUnsafe { + ty, + reason: msg!("box cannot be represented as a single pointer"), + help: None, + }; + } + } else { + // (mid-retcon-commit-chain comment:) + // this is the original fallback behavior, which is wrong + if let ty::Adt(def, args) = ty.kind() { + self.visit_struct_or_union(state, ty, *def, args) + } else if cfg!(debug_assertions) { + bug!("ImproperCTypes: this retcon commit was badly written") + } else { + FfiSafe + } + } + } + IndirectionKind::Ref | IndirectionKind::RawPtr => { + // Weird behaviour for pointee safety. the big question here is + // "if you have a FFI-unsafe pointee behind a FFI-safe pointer type, is it ok?" + // The answer until now is: + // "It's OK for rust-defined functions and callbacks, we'll assume those are + // meant to be opaque types on the other side of the FFI boundary". + // + // Reasoning: + // For extern function declarations, the actual definition of the function is + // written somewhere else, meaning the declaration is free to express this + // opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void + // (opaque callee-side). For extern function definitions, however, in the case + // where the type is opaque caller-side, it is not opaque callee-side, + // and having the full type information is necessary to compile the function. + // + // It might be better to rething this, or even ignore pointee safety for a first + // batch of behaviour changes. See the discussion that ends with + // https://github.com/rust-lang/rust/pull/134697#issuecomment-2692610258 + if (state.is_in_defined_function() || state.is_in_fnptr()) + && inner_ty.is_sized(self.cx.tcx, self.cx.typing_env()) + { + FfiSafe + } else { + self.visit_type(state, inner_ty) + } + } + } + } + /// Checks if the given `VariantDef`'s field types are "ffi-safe". fn visit_variant_fields( &mut self, @@ -477,7 +560,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } if def.non_enum_variant().fields.is_empty() { - return FfiUnsafe { + FfiUnsafe { ty, reason: if def.is_struct() { msg!("this struct has no fields") @@ -489,9 +572,10 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } else { Some(msg!("consider adding a member to this union")) }, - }; + } + } else { + self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) } - self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) } fn visit_enum( @@ -559,23 +643,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { match *ty.kind() { ty::Adt(def, args) => { - if let Some(boxed) = ty.boxed_ty() - && ( - // FIXME(ctypes): this logic is broken, but it still fits the current tests - state.is_in_defined_function() - || (state.is_in_fnptr() - && matches!(self.base_fn_mode, CItemKind::Definition)) - ) - { - if boxed.is_sized(tcx, self.cx.typing_env()) { - return FfiSafe; - } else { - return FfiUnsafe { - ty, - reason: msg!("box cannot be represented as a single pointer"), - help: None, - }; - } + if let Some(inner_ty) = ty.boxed_ty() { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::Box); } if def.is_phantom_data() { return FfiPhantom(ty); @@ -639,15 +708,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: Some(msg!("consider using a struct instead")), }, - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) - if { - (state.is_in_defined_function() || state.is_in_fnptr()) - && ty.is_sized(self.cx.tcx, self.cx.typing_env()) - } => - { - FfiSafe - } - ty::RawPtr(ty, _) if match ty.kind() { ty::Tuple(tuple) => tuple.is_empty(), @@ -657,7 +717,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.visit_type(state, ty), + ty::RawPtr(inner_ty, _) => { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::RawPtr); + } + ty::Ref(_, inner_ty, _) => { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::Ref); + } ty::Array(inner_ty, _) => self.visit_type(state, inner_ty), From 7bb9851ae8f0825109f0529330334f4df9c029f3 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sat, 24 Jan 2026 13:37:53 +0100 Subject: [PATCH 10/10] ImproperCTypes: remove special cases through better state tracking Another interal change that shouldn't impact rustc users. Code called outside of `visit_type` (and callees) is moved inside, by adding new types to properly track the state of a type visitation. - OuterTyKind tracks the knowledge of the type "directly outside of" the one being visited (if we are visiting a struct's field, an array's element, etc) - RootUseFlags tracks the knowledge of how the "original type being visited" is used: static variable, function argument/return, etc. --- .../rustc_lint/src/types/improper_ctypes.rs | 135 +++++++++++------- 1 file changed, 85 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 123b62cfdfe94..cfcd199ed520d 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -369,6 +369,35 @@ impl VisitorState { } } +bitflags! { + /// Data that summarises how an "outer type" surrounds its inner type(s) + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct OuterTyData: u8 { + /// To show that there is no outer type, the current type is directly used by a `static` + /// variable or a function/FnPtr + const NO_OUTER_TY = 0b01; + /// For NO_OUTER_TY cases, show that we are being directly used by a FnPtr specifically + /// FIXME(ctypes): this is only used for "bad behaviour" reproduced for compatibility's sake + const NO_OUTER_TY_FNPTR = 0b10; + } +} + +impl OuterTyData { + /// Get the proper data for a given outer type. + fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { + match ty.kind() { + ty::FnPtr(..) => Self::NO_OUTER_TY | Self::NO_OUTER_TY_FNPTR, + ty::RawPtr(..) + | ty::Ref(..) + | ty::Adt(..) + | ty::Tuple(..) + | ty::Array(..) + | ty::Slice(_) => Self::empty(), + k @ _ => bug!("unexpected outer type {:?} of kind {:?}", ty, k), + } + } +} + /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. /// It uses ``check_*`` methods as entrypoints to be called elsewhere, /// and ``visit_*`` methods to recurse. @@ -454,7 +483,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { { FfiSafe } else { - self.visit_type(state, inner_ty) + self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) } } } @@ -475,7 +504,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. let field_ty = get_type_from_field(self.cx, field, args); - match self.visit_type(state, field_ty) { + match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -494,7 +523,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { let field_ty = get_type_from_field(self.cx, field, args); - all_phantom &= match self.visit_type(state, field_ty) { + all_phantom &= match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -592,11 +621,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Empty enums are okay... although sort of useless. return FfiSafe; } - // Check for a repr() attribute to specify the size of the discriminant. + // Check for a repr() attribute to specify the size of the + // discriminant. if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { // Special-case types like `Option` and `Result` - if let Some(ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { - return self.visit_type(state, ty); + if let Some(inner_ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { + return self.visit_type(state, OuterTyData::from_ty(ty), inner_ty); } return FfiUnsafe { @@ -628,7 +658,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). - fn visit_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + fn visit_type( + &mut self, + state: VisitorState, + outer_ty: OuterTyData, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { use FfiResult::*; let tcx = self.cx.tcx; @@ -672,7 +707,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Pattern types are just extra invariants on the type that you need to uphold, // but only the base type is relevant for being representable in FFI. // (note: this lint was written when pattern types could only be integers constrained to ranges) - ty::Pat(pat_ty, _) => self.visit_type(state, pat_ty), + ty::Pat(pat_ty, _) => self.visit_type(state, outer_ty, pat_ty), // types which likely have a stable representation, if the target architecture defines those // note: before rust 1.77, 128-bit ints were not FFI-safe on x86_64 @@ -702,11 +737,22 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: Some(msg!("consider using `*const u8` and a length instead")), }, - ty::Tuple(..) => FfiUnsafe { - ty, - reason: msg!("tuples have unspecified layout"), - help: Some(msg!("consider using a struct instead")), - }, + ty::Tuple(tuple) => { + // C functions can return void + let empty_and_safe = tuple.is_empty() + && outer_ty.contains(OuterTyData::NO_OUTER_TY) + && state.is_in_function_return(); + + if empty_and_safe { + FfiSafe + } else { + FfiUnsafe { + ty, + reason: msg!("tuples have unspecified layout"), + help: Some(msg!("consider using a struct instead")), + } + } + } ty::RawPtr(ty, _) if match ty.kind() { @@ -724,7 +770,25 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return self.visit_indirection(state, ty, inner_ty, IndirectionKind::Ref); } - ty::Array(inner_ty, _) => self.visit_type(state, inner_ty), + ty::Array(inner_ty, _) => { + if state.is_in_function() + && outer_ty.contains(OuterTyData::NO_OUTER_TY) + // FIXME(ctypes): VVV-this-VVV shouldn't be the case + && !outer_ty.contains(OuterTyData::NO_OUTER_TY_FNPTR) + { + // C doesn't really support passing arrays by value - the only way to pass an array by value + // is through a struct. + FfiResult::FfiUnsafe { + ty, + reason: msg!("passing raw arrays by value is not FFI-safe"), + help: Some(msg!("consider passing a pointer to the array")), + } + } else { + // let's allow phantoms to go through, + // since an array of 1-ZSTs is also a 1-ZST + self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) + } + } ty::FnPtr(sig_tys, hdr) => { let sig = sig_tys.with(hdr); @@ -740,18 +804,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let sig = tcx.instantiate_bound_regions_with_erased(sig); for arg in sig.inputs() { - match self.visit_type(VisitorState::ARGUMENT_TY_IN_FNPTR, *arg) { + match self.visit_type( + VisitorState::ARGUMENT_TY_IN_FNPTR, + OuterTyData::from_ty(ty), + *arg, + ) { FfiSafe => {} r => return r, } } let ret_ty = sig.output(); - if ret_ty.is_unit() { - return FfiSafe; - } - self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, ret_ty) + self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, OuterTyData::from_ty(ty), ret_ty) } ty::Foreign(..) => FfiSafe, @@ -819,43 +884,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }) } - /// Check if the type is array and emit an unsafe type lint. - fn check_for_array_ty(&mut self, ty: Ty<'tcx>) -> PartialFfiResult<'tcx> { - if let ty::Array(..) = ty.kind() { - Some(FfiResult::FfiUnsafe { - ty, - reason: msg!("passing raw arrays by value is not FFI-safe"), - help: Some(msg!("consider passing a pointer to the array")), - }) - } else { - None - } - } - - /// Determine the FFI-safety of a single (MIR) type, given the context of how it is used. fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); if let Some(res) = self.visit_for_opaque_ty(ty) { return res; } - // C doesn't really support passing arrays by value - the only way to pass an array by value - // is through a struct. So, first test that the top level isn't an array, and then - // recursively check the types inside. - if state.is_in_function() { - if let Some(res) = self.check_for_array_ty(ty) { - return res; - } - } - - // Don't report FFI errors for unit return types. This check exists here, and not in - // the caller (where it would make more sense) so that normalization has definitely - // happened. - if state.is_in_function_return() && ty.is_unit() { - return FfiResult::FfiSafe; - } - - self.visit_type(state, ty) + self.visit_type(state, OuterTyData::NO_OUTER_TY, ty) } }