Skip to content

Commit e0ebb0a

Browse files
committed
Document C macro generation and runtime contracts
Explain handwritten declarations, private helpers, modules, and tests across the C macro pipeline. Document the runtime access and conversion contracts while leaving generated snapshots unchanged.
1 parent cc28982 commit e0ebb0a

126 files changed

Lines changed: 7712 additions & 208 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎pgrx-bindgen/src/build.rs‎

Lines changed: 149 additions & 2 deletions
Large diffs are not rendered by default.

‎pgrx-bindgen/src/build/binding_symbols.rs‎

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,27 @@
22
//LICENSE
33
//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
44

5+
//! Extract the Rust half of the C macro type contract from fresh bindgen output.
6+
//!
7+
//! The frontend supplies C declarations and target facts; this collector records
8+
//! what the current binding build actually exposes: paths, storage, signatures,
9+
//! record fields, and integer values. Emission must reconcile both catalogs before
10+
//! using a Rust symbol. Ambiguous or unrepresentable declarations are omitted
11+
//! rather than repaired, so the transpiler can report a skip without changing
12+
//! bindgen's output. Checked-in documentation bindings are not semantic inputs.
13+
14+
/// Use the emitter catalog and result types that connect C inspection to generated binding
15+
/// publication.
516
use pgrx_c_macros::{
617
AliasBinding, BindingCatalog, BitfieldBinding, DeclarationCatalog, EnumBinding, FieldBinding,
718
FunctionBinding, IntegerBinding, IntegerBindingRepresentation, IntegerConstant, IntegerKind,
819
IntegerValue, RecordBinding, RecordKind, RustBindingType, TargetFacts, VariableBinding,
920
};
21+
/// Keep binding catalogs, header trees, and output maps ordered for deterministic generation
22+
/// and collision checks.
1023
use std::collections::{BTreeMap, BTreeSet};
24+
/// Inspect actual Rust syntax and spans instead of reconstructing binding or generated-item
25+
/// facts from names.
1126
use syn::{Expr, Item, Lit, Type, UnOp};
1227

1328
/// Discover bindgen's actual values and paths without changing its bindings.
@@ -72,6 +87,8 @@ pub(super) fn collect_bindings(
7287
bindings
7388
}
7489

90+
/// Recover bit offsets and widths from actual bindgen getter bodies and verified storage units;
91+
/// getter names alone are not layout evidence.
7592
fn collect_bitfields(
7693
items: &[Item],
7794
path: &mut Vec<String>,
@@ -178,9 +195,18 @@ fn collect_bitfields(
178195
}
179196
}
180197

198+
/// Collect literal storage-unit reads from a bindgen getter so ambiguous or unrelated methods
199+
/// cannot establish bitfield layout.
181200
#[derive(Default)]
182-
struct BitfieldAccess(Vec<(String, u64, u32)>);
201+
struct BitfieldAccess(
202+
/// Observed storage field, bit offset, and width tuples collected from the getter body.
203+
Vec<(String, u64, u32)>,
204+
);
205+
/// Recover bitfield layout from actual getter access syntax rather than method naming
206+
/// conventions.
183207
impl syn::visit_mut::VisitMut for BitfieldAccess {
208+
/// Record literal bindgen storage-unit reads while visiting the rest of the getter body,
209+
/// allowing ambiguous access shapes to be rejected.
184210
fn visit_expr_method_call_mut(&mut self, call: &mut syn::ExprMethodCall) {
185211
if call.method == "get"
186212
&& call.args.len() == 2
@@ -198,12 +224,16 @@ impl syn::visit_mut::VisitMut for BitfieldAccess {
198224
}
199225
}
200226

227+
/// Accept only a literal unsigned value for layout metadata rather than evaluating arbitrary
228+
/// Rust expressions.
201229
fn literal_u64(expression: &Expr) -> Option<u64> {
202230
let Expr::Lit(literal) = expression else { return None };
203231
let Lit::Int(literal) = &literal.lit else { return None };
204232
literal.base10_parse().ok()
205233
}
206234

235+
/// Recognize a bindgen bitfield storage unit with concrete byte-array storage so reported
236+
/// ranges can be bounded.
207237
fn bitfield_unit_bytes(ty: &Type) -> Option<u64> {
208238
let Type::Path(ty) = ty else { return None };
209239
let segment = ty.path.segments.last()?;
@@ -228,14 +258,23 @@ fn bitfield_unit_bytes(ty: &Type) -> Option<u64> {
228258
/// collisions between public modules cannot accidentally select one C declaration.
229259
#[derive(Default)]
230260
struct StructuredDuplicates {
261+
/// Foreign function names that cannot be selected unambiguously.
231262
functions: BTreeSet<String>,
263+
/// Record names duplicated across accessible public modules.
232264
records: BTreeSet<String>,
265+
/// Type alias paths repeated in the collected type namespace.
233266
types: BTreeSet<String>,
267+
/// Enum names whose representation cannot be chosen uniquely.
234268
enums: BTreeSet<String>,
269+
/// Foreign variable names that occur at multiple accessible paths.
235270
variables: BTreeSet<String>,
236271
}
237272

273+
/// Remove ambiguous bindings only after scanning the entire namespace, so traversal order
274+
/// cannot select a winner.
238275
impl StructuredDuplicates {
276+
/// Remove all names observed more than once in a structured namespace so emission cannot
277+
/// choose an arbitrary binding.
239278
fn remove(self, bindings: &mut BindingCatalog) {
240279
for name in self.functions {
241280
bindings.functions.remove(&name);
@@ -255,6 +294,8 @@ impl StructuredDuplicates {
255294
}
256295
}
257296

297+
/// Insert a binding while remembering duplicate names for deferred removal after the full
298+
/// catalog scan.
258299
fn insert_unique<T>(
259300
map: &mut BTreeMap<String, T>,
260301
duplicates: &mut BTreeSet<String>,
@@ -266,14 +307,17 @@ fn insert_unique<T>(
266307
}
267308
}
268309

310+
/// Restrict the catalog to bindings generated code can name through public paths.
269311
fn public(vis: &syn::Visibility) -> bool {
270312
matches!(vis, syn::Visibility::Public(_))
271313
}
272314

315+
/// Remove raw-identifier syntax for C-name matching while keeping actual Rust paths separately.
273316
fn rust_name(ident: &syn::Ident) -> String {
274317
ident.to_string().trim_start_matches("r#").to_owned()
275318
}
276319

320+
/// Extend the current public module path with the item's actual Rust identifier.
277321
fn item_path(path: &[String], ident: &syn::Ident) -> Vec<String> {
278322
let mut result = path.to_vec();
279323
result.push(ident.to_string());
@@ -349,6 +393,8 @@ fn flexible_array_helpers(items: &[Item], path: &mut Vec<String>) -> BTreeSet<Ve
349393
verified
350394
}
351395

396+
/// Walk public bindings and record callable signatures, variables, aliases, record storage, and
397+
/// enum representation for C/Rust reconciliation.
352398
fn collect_structures(
353399
items: &[Item],
354400
path: &mut Vec<String>,
@@ -498,6 +544,8 @@ fn collect_structures(
498544
}
499545
}
500546

547+
/// Capture named public field storage and representation attributes without claiming the
548+
/// corresponding C layout is already verified.
501549
fn record_binding(
502550
ident: &syn::Ident,
503551
attrs: &[syn::Attribute],
@@ -527,6 +575,7 @@ fn record_binding(
527575
RecordBinding { path: item_path(path, ident), kind, fields, packed, copy }
528576
}
529577

578+
/// Parse representation and derive metadata used to establish actual Rust storage properties.
530579
fn attribute_arguments(attrs: &[syn::Attribute], name: &str) -> Vec<syn::Meta> {
531580
attrs
532581
.iter()
@@ -542,6 +591,8 @@ fn attribute_arguments(attrs: &[syn::Attribute], name: &str) -> Vec<syn::Meta> {
542591
.collect()
543592
}
544593

594+
/// Record integral Rust enum storage and exact variant values, rejecting payload variants or
595+
/// unevaluable discriminants.
545596
fn enum_binding(
546597
item: &syn::ItemEnum,
547598
path: &[String],
@@ -573,11 +624,15 @@ fn enum_binding(
573624
Some(EnumBinding { path: item_path(path, &item.ident), repr, variants })
574625
}
575626

627+
/// Keep the declared calling convention, including Rust defaults, so the emitter can reject an
628+
/// incompatible C call.
576629
fn abi_name(abi: Option<&syn::Abi>) -> String {
577630
abi.map(|abi| abi.name.as_ref().map_or_else(|| "C".into(), syn::LitStr::value))
578631
.unwrap_or_else(|| "Rust".into())
579632
}
580633

634+
/// Recover a foreign symbol's linker spelling so ordinary and cshim-backed calls remain
635+
/// distinguishable.
581636
fn link_name(attrs: &[syn::Attribute]) -> Option<String> {
582637
attrs.iter().find_map(|attribute| {
583638
let syn::Meta::NameValue(meta) = &attribute.meta else { return None };
@@ -590,6 +645,7 @@ fn link_name(attrs: &[syn::Attribute]) -> Option<String> {
590645
})
591646
}
592647

648+
/// Represent omitted Rust returns as unit and recursively catalog explicit return storage.
593649
fn return_type(
594650
output: &syn::ReturnType,
595651
path: &[String],
@@ -603,6 +659,9 @@ fn return_type(
603659
}
604660
}
605661

662+
/// Translate supported Rust binding storage into the catalog while preserving pointer
663+
/// mutability, callback ABI, wrappers, and named paths; reject unknown or excessively deep
664+
/// forms.
606665
fn binding_type(
607666
ty: &Type,
608667
path: &[String],
@@ -753,6 +812,8 @@ fn binding_type(
753812
}
754813
}
755814

815+
/// Resolve relative public binding paths without letting a path escape the owning crate
816+
/// namespace.
756817
fn resolve_path(parts: &[String], current: &[String]) -> Option<Vec<String>> {
757818
let mut resolved = current.to_vec();
758819
let mut position = 0;
@@ -777,6 +838,8 @@ fn resolve_path(parts: &[String], current: &[String]) -> Option<Vec<String>> {
777838
Some(resolved)
778839
}
779840

841+
/// Walk public integer bindings and retain representable values and paths, including associated
842+
/// enum constants and checked OID storage.
780843
fn collect(
781844
items: &[Item],
782845
path: &mut Vec<String>,
@@ -833,14 +896,23 @@ fn collect(
833896
}
834897
}
835898

899+
/// Represent verified integer storage bounds independently of the C semantic kind used by the
900+
/// emitter.
836901
#[derive(Clone, Copy)]
837902
struct Storage {
903+
/// Whether this Rust storage can represent negative C constants.
838904
signed: bool,
905+
/// Width verified from primitive spelling or the selected target's standard C aliases.
839906
bits: u32,
907+
/// Whether storage accepts only zero or one rather than the full integer range.
840908
boolean: bool,
841909
}
842910

911+
/// Enforce the actual Rust storage range before a binding value can participate in symbolic C
912+
/// emission.
843913
impl Storage {
914+
/// Check that the bindgen value fits its actual storage before it can be referenced as a C
915+
/// constant.
844916
fn contains(self, value: i128) -> bool {
845917
if self.boolean {
846918
return (0..=1).contains(&value);
@@ -854,6 +926,8 @@ impl Storage {
854926
}
855927
}
856928

929+
/// Recognize primitive or standard-library C integer storage using target widths; arbitrary
930+
/// same-spelled aliases provide no proof.
857931
fn storage(
858932
ty: &Type,
859933
aliases: &BTreeMap<String, &Type>,
@@ -925,13 +999,17 @@ fn storage(
925999
})
9261000
}
9271001

1002+
/// Promote a recorded integer value to i128 for bounded signed arithmetic during literal
1003+
/// interpretation.
9281004
fn numeric(value: IntegerValue) -> i128 {
9291005
match value {
9301006
IntegerValue::Signed(value) => value.into(),
9311007
IntegerValue::Unsigned(value) => value.into(),
9321008
}
9331009
}
9341010

1011+
/// Read literal, boolean, negated, and grouped binding values without substituting a newly
1012+
/// evaluated expression for bindgen's output.
9351013
fn rust_value(expr: &Expr) -> Option<IntegerValue> {
9361014
match expr {
9371015
Expr::Lit(expr) => match &expr.lit {
@@ -948,12 +1026,29 @@ fn rust_value(expr: &Expr) -> Option<IntegerValue> {
9481026
}
9491027
}
9501028

1029+
/// Check catalog, publication, and invalidation invariants directly against the private
1030+
/// binding-build implementation.
9511031
#[cfg(test)]
9521032
mod tests {
1033+
//! Check catalog, publication, and invalidation invariants directly against the private
1034+
//! binding-build implementation.
1035+
//!
1036+
//! The tests inspect real parsed output, generated leaf maps, and compiler dependency
1037+
//! directives. Temporary input trees make success and rejection conditions explicit without
1038+
//! relying on a configured server.
1039+
1040+
/// Exercise the private implementation directly in its unit tests without widening the
1041+
/// production API.
9531042
use super::*;
1043+
/// Use the emitter catalog and result types that connect C inspection to generated binding
1044+
/// publication.
9541045
use pgrx_c_macros::{ByteOrder, IntegerType, TypeCategory, TypeInfo};
1046+
/// Render parsed fixture syntax for the independent generated consumer without rewriting
1047+
/// semantic declarations.
9551048
use quote::ToTokens;
9561049

1050+
/// Construct explicit target integer facts for collector tests so storage checks do not
1051+
/// depend on the test host.
9571052
fn target() -> TargetFacts {
9581053
TargetFacts {
9591054
triple: "x86_64-unknown-linux-gnu".into(),
@@ -979,6 +1074,8 @@ mod tests {
9791074
}
9801075
}
9811076

1077+
/// Construct a compiler-side integer constant used to test reconciliation with
1078+
/// independently written binding syntax.
9821079
fn constant(kind: IntegerKind, value: IntegerValue) -> IntegerConstant {
9831080
IntegerConstant {
9841081
ty: TypeInfo {
@@ -995,6 +1092,8 @@ mod tests {
9951092
}
9961093
}
9971094

1095+
/// Checks actual bindgen values, primitive storage, and enum/OID paths without replacing
1096+
/// them with Clang's preferred values.
9981097
#[test]
9991098
fn preserves_bindgen_values_storage_and_enum_oid_paths() {
10001099
let file = syn::parse_file(
@@ -1053,6 +1152,8 @@ pub mod Negative { pub type Type = ::core::ffi::c_int; pub const NEG: Type = -1;
10531152
));
10541153
}
10551154

1155+
/// Checks that associated enum variants retain actual Rust paths and values for later C
1156+
/// disagreement validation.
10561157
#[test]
10571158
fn collects_rustified_enum_symbols_without_replacing_bindgen_values() {
10581159
let file = syn::parse_file(
@@ -1083,6 +1184,8 @@ pub mod B { #[repr(u32)] pub enum Second { DUP = 1 } }
10831184
assert_eq!(file.to_token_stream().to_string(), original);
10841185
}
10851186

1187+
/// Checks that duplicate paths, unproved storage, and nonintegral values are omitted
1188+
/// instead of guessed.
10861189
#[test]
10871190
fn rejects_ambiguous_paths_unresolved_storage_and_nonintegral_constants() {
10881191
let file = syn::parse_file(
@@ -1112,6 +1215,8 @@ pub const CYCLE: Loop = 7;
11121215
);
11131216
}
11141217

1218+
/// Checks that the catalog preserves actual foreign ABI, aliases, record fields, and nested
1219+
/// callback storage for reconciliation.
11151220
#[test]
11161221
fn catalogs_actual_function_abi_aliases_records_and_callback_types() {
11171222
let file = syn::parse_file(
@@ -1206,6 +1311,8 @@ unsafe extern "C" {
12061311
assert!(!catalog.variables["Frozen"].mutable);
12071312
}
12081313

1314+
/// Checks storage-unit widths and offsets from genuine getter accesses while rejecting
1315+
/// unrelated getter-shaped methods.
12091316
#[test]
12101317
fn recovers_bitfield_layout_from_actual_bindgen_getter_bodies() {
12111318
let file = syn::parse_file(
@@ -1232,6 +1339,8 @@ impl Bits {
12321339
assert_eq!(flag.ty, RustBindingType::Integer { signed: false, bits: 32 });
12331340
}
12341341

1342+
/// Checks standard-library C aliases inside function signatures and fields using the
1343+
/// selected target facts.
12351344
#[test]
12361345
fn collects_actual_std_os_raw_function_and_field_storage() {
12371346
let file = syn::parse_file(
@@ -1258,6 +1367,8 @@ unsafe extern "C" {
12581367
);
12591368
}
12601369

1370+
/// Checks that ambiguous or unsupported signatures and record forms cannot enter the usable
1371+
/// binding catalog.
12611372
#[test]
12621373
fn rejects_ambiguous_and_unrepresentable_structured_bindings() {
12631374
let file = syn::parse_file(
@@ -1288,6 +1399,8 @@ unsafe extern "C" {
12881399
matches!(&catalog.functions["good"].result, RustBindingType::Pointer { pointee, .. } if **pointee == RustBindingType::Named { path: vec!["core".into(), "ffi".into(), "c_void".into()] })
12891400
);
12901401
}
1402+
/// Checks generic storage helpers from their emitted definitions rather than trusting
1403+
/// conventional helper names.
12911404
#[test]
12921405
fn verifies_generic_union_and_flexible_array_storage_definitions() {
12931406
let file = syn::parse_file(r#"

0 commit comments

Comments
 (0)