1use padlock_core::arch::ArchConfig;
8use padlock_core::ir::{Field, StructLayout, TypeInfo};
9use tree_sitter::{Node, Parser};
10
11fn is_zig_comptime_type(ty: &str) -> bool {
18 matches!(
19 ty.trim(),
20 "type" | "anytype" | "comptime_int" | "comptime_float"
21 )
22}
23
24fn zig_type_size_align(ty: &str, arch: &'static ArchConfig) -> (usize, usize) {
25 let ty = ty.trim();
26 match ty {
27 "bool" => (1, 1),
28 "u8" | "i8" => (1, 1),
29 "u16" | "i16" | "f16" => (2, 2),
30 "u32" | "i32" | "f32" => (4, 4),
31 "u64" | "i64" | "f64" => (8, 8),
32 "u128" | "i128" | "f128" => (16, 16),
33 "f80" => (10, 16),
35 "usize" | "isize" => (arch.pointer_size, arch.pointer_size),
36 "void" | "anyopaque" => (0, 1),
37 "type" | "anytype" | "comptime_int" | "comptime_float" => {
39 (arch.pointer_size, arch.pointer_size)
40 }
41
42 "c_char" | "c_uchar" | "c_schar" => (1, 1),
44 "c_short" | "c_ushort" => (2, 2),
45 "c_int" | "c_uint" => (4, 4),
46 "c_long" | "c_ulong" => (arch.pointer_size, arch.pointer_size), "c_longlong" | "c_ulonglong" => (8, 8),
48 "c_float" => (4, 4),
49 "c_double" => (8, 8),
50 "c_longdouble" => (16, 16),
51
52 ty if (ty.starts_with('u') || ty.starts_with('i'))
58 && ty[1..].bytes().all(|b| b.is_ascii_digit())
59 && !ty[1..].is_empty() =>
60 {
61 if let Ok(bits) = ty[1..].parse::<usize>() {
62 let bytes = bits.div_ceil(8).max(1);
63 let align = bytes.next_power_of_two().min(8);
65 (bytes, align)
66 } else {
67 (arch.pointer_size, arch.pointer_size)
68 }
69 }
70
71 _ => (arch.pointer_size, arch.pointer_size),
72 }
73}
74
75fn zig_type_bit_width(ty: &str, arch: &'static ArchConfig) -> usize {
81 let ty = ty.trim();
82 if (ty.starts_with('u') || ty.starts_with('i'))
84 && !ty[1..].is_empty()
85 && ty[1..].bytes().all(|b| b.is_ascii_digit())
86 && let Ok(bits) = ty[1..].parse::<usize>()
87 {
88 return bits;
89 }
90 match ty {
91 "bool" => 1,
92 "f16" => 16,
93 "f32" => 32,
94 "f64" => 64,
95 "f80" => 80,
96 "f128" => 128,
97 _ => {
98 let (bytes, _) = zig_type_size_align(ty, arch);
100 bytes * 8
101 }
102 }
103}
104
105fn type_node_size_align(source: &str, node: Node<'_>, arch: &'static ArchConfig) -> (usize, usize) {
107 match node.kind() {
108 "builtin_type" | "identifier" => {
109 let text = source[node.byte_range()].trim();
110 zig_type_size_align(text, arch)
111 }
112 "pointer_type" => (arch.pointer_size, arch.pointer_size),
114 "nullable_type" => {
117 if let Some(inner) = find_child_by_kinds(node, &["pointer_type"]) {
119 let _ = inner; (arch.pointer_size, arch.pointer_size)
121 } else if let Some(inner) = find_first_type_child(source, node) {
122 let (sz, al) = type_node_size_align(source, inner, arch);
123 let tagged = (sz + 1).next_multiple_of(al.max(1));
125 (tagged, al.max(1))
126 } else {
127 (arch.pointer_size, arch.pointer_size)
128 }
129 }
130 "slice_type" => (arch.pointer_size * 2, arch.pointer_size),
132 "array_type" => {
134 if let Some((count, elem_sz, elem_al)) = parse_array_type(source, node, arch) {
135 (elem_sz * count, elem_al)
136 } else {
137 (arch.pointer_size, arch.pointer_size)
138 }
139 }
140 "error_union" => (arch.pointer_size * 2, arch.pointer_size),
142 _ => (arch.pointer_size, arch.pointer_size),
143 }
144}
145
146fn parse_array_type(
148 source: &str,
149 node: Node<'_>,
150 arch: &'static ArchConfig,
151) -> Option<(usize, usize, usize)> {
152 let mut count: Option<usize> = None;
154 let mut elem: Option<(usize, usize)> = None;
155
156 for i in 0..node.child_count() {
157 let child = node.child(i)?;
158 match child.kind() {
159 "integer" | "integer_literal" => {
160 let text = source[child.byte_range()].trim();
161 count = text.parse::<usize>().ok();
162 }
163 "builtin_type" | "identifier" | "pointer_type" | "slice_type" | "array_type"
164 | "nullable_type" => {
165 elem = Some(type_node_size_align(source, child, arch));
166 }
167 _ => {}
168 }
169 }
170
171 let count = count?;
172 let (esz, eal) = elem.unwrap_or((arch.pointer_size, arch.pointer_size));
173 Some((count, esz, eal))
174}
175
176fn find_child_by_kinds<'a>(node: Node<'a>, kinds: &[&str]) -> Option<Node<'a>> {
177 for i in 0..node.child_count() {
178 if let Some(c) = node.child(i)
179 && kinds.contains(&c.kind())
180 {
181 return Some(c);
182 }
183 }
184 None
185}
186
187fn find_first_type_child<'a>(source: &str, node: Node<'a>) -> Option<Node<'a>> {
188 let _ = source;
189 for i in 0..node.child_count() {
190 if let Some(c) = node.child(i) {
191 match c.kind() {
192 "builtin_type" | "identifier" | "pointer_type" | "slice_type" | "array_type"
193 | "nullable_type" | "error_union" => return Some(c),
194 _ => {}
195 }
196 }
197 }
198 None
199}
200
201fn extract_structs(source: &str, root: Node<'_>, arch: &'static ArchConfig) -> Vec<StructLayout> {
204 let mut layouts = Vec::new();
205 let mut stack = vec![root];
206
207 while let Some(node) = stack.pop() {
208 for i in (0..node.child_count()).rev() {
209 if let Some(c) = node.child(i) {
210 stack.push(c);
211 }
212 }
213
214 if node.kind() == "variable_declaration"
215 && let Some(layout) = parse_variable_declaration(source, node, arch)
216 {
217 layouts.push(layout);
218 } else if node.kind() == "function_declaration" {
219 note_comptime_generic_fn(source, node);
224 }
225 }
226 layouts
227}
228
229fn note_comptime_generic_fn(source: &str, node: Node<'_>) {
233 let fn_name = (0..node.child_count())
235 .filter_map(|i| node.child(i))
236 .find(|c| c.kind() == "identifier")
237 .map(|c| source[c.byte_range()].to_string());
238
239 let Some(fn_name) = fn_name else {
240 return;
241 };
242
243 let has_comptime_param = (0..node.child_count())
245 .filter_map(|i| node.child(i))
246 .filter(|c| c.kind() == "parameters")
247 .any(|params| {
248 (0..params.child_count())
249 .filter_map(|j| params.child(j))
250 .any(|param| {
251 (0..param.child_count())
253 .filter_map(|k| param.child(k))
254 .any(|tok| tok.kind() == "comptime")
255 })
256 });
257
258 if !has_comptime_param {
259 return;
260 }
261
262 let returns_type = (0..node.child_count())
264 .filter_map(|i| node.child(i))
265 .any(|c| {
266 source[c.byte_range()].trim() == "type"
269 && matches!(c.kind(), "identifier" | "primitive_type" | "builtin_type")
270 });
271
272 if returns_type {
273 eprintln!(
274 "padlock: note: skipping '{fn_name}' — comptime-generic function \
275 (returns a struct type; layout depends on type arguments)"
276 );
277 crate::record_skipped(
278 &fn_name,
279 "comptime-generic function — returns a struct type; \
280 layout depends on type arguments",
281 );
282 }
283}
284
285fn parse_variable_declaration(
286 source: &str,
287 node: Node<'_>,
288 arch: &'static ArchConfig,
289) -> Option<StructLayout> {
290 let source_line = node.start_position().row as u32 + 1;
291 let decl_start_byte = node.start_byte();
292 let mut name: Option<String> = None;
293 let mut struct_node: Option<Node> = None;
294 let mut union_node: Option<Node> = None;
295
296 for i in 0..node.child_count() {
297 let child = node.child(i)?;
298 match child.kind() {
299 "identifier" if name.is_none() => {
300 name = Some(source[child.byte_range()].to_string());
302 }
303 "struct_declaration" => struct_node = Some(child),
304 "union_declaration" => union_node = Some(child),
305 _ => {}
306 }
307 }
308
309 let name = name?;
310 let mut layout = if let Some(sn) = struct_node {
311 parse_struct_declaration(source, sn, name, arch, source_line)?
312 } else if let Some(un) = union_node {
313 parse_union_declaration(source, un, name, arch, source_line)?
314 } else {
315 return None;
316 };
317 layout.suppressed_findings =
318 super::suppress::suppressed_from_preceding_source(source, decl_start_byte);
319 Some(layout)
320}
321
322fn parse_union_declaration(
330 source: &str,
331 node: Node<'_>,
332 name: String,
333 arch: &'static ArchConfig,
334 source_line: u32,
335) -> Option<StructLayout> {
336 let mut is_tagged = false;
337 let mut raw_fields: Vec<(String, String, usize, usize, u32)> = Vec::new();
338
339 for i in 0..node.child_count() {
340 let child = node.child(i)?;
341 match child.kind() {
342 "enum" => is_tagged = true,
344 "container_field" => {
348 if let Some(f) = parse_container_field(source, child, arch, false) {
349 raw_fields.push(f);
350 }
351 }
352 _ => {}
353 }
354 }
355
356 if raw_fields.is_empty() {
357 return None;
358 }
359
360 let max_size = raw_fields
362 .iter()
363 .map(|(_, _, sz, _, _)| *sz)
364 .max()
365 .unwrap_or(0);
366 let max_align = raw_fields
367 .iter()
368 .map(|(_, _, _, al, _)| *al)
369 .max()
370 .unwrap_or(1);
371 let total_size = if max_align > 0 {
372 max_size.next_multiple_of(max_align)
373 } else {
374 max_size
375 };
376
377 let mut fields: Vec<Field> = raw_fields
378 .into_iter()
379 .map(|(fname, type_text, size, align, field_line)| Field {
380 name: fname,
381 ty: TypeInfo::Primitive {
382 name: type_text,
383 size,
384 align,
385 },
386 offset: 0,
387 size,
388 align,
389 source_file: None,
390 source_line: Some(field_line),
391 access: padlock_core::ir::AccessPattern::Unknown,
392 })
393 .collect();
394
395 if is_tagged {
398 let n = fields.len();
399 let tag_size: usize = if n <= 256 {
400 1
401 } else if n <= 65536 {
402 2
403 } else {
404 4
405 };
406 fields.push(Field {
407 name: "__tag".to_string(),
408 ty: TypeInfo::Primitive {
409 name: format!("u{}", tag_size * 8),
410 size: tag_size,
411 align: tag_size,
412 },
413 offset: total_size, size: tag_size,
415 align: tag_size,
416 source_file: None,
417 source_line: None,
418 access: padlock_core::ir::AccessPattern::Unknown,
419 });
420 }
421
422 let struct_align = max_align; let final_size = if is_tagged {
425 let tag_size = fields.last().map(|f| f.size).unwrap_or(0);
426 (total_size + tag_size).next_multiple_of(struct_align.max(1))
427 } else {
428 total_size
429 };
430
431 Some(StructLayout {
432 name,
433 total_size: final_size,
434 align: struct_align,
435 fields,
436 source_file: None,
437 source_line: Some(source_line),
438 arch,
439 is_packed: false,
440 is_union: true,
441 is_repr_rust: false,
442 suppressed_findings: Vec::new(), uncertain_fields: Vec::new(),
444 })
445}
446
447fn parse_struct_declaration(
448 source: &str,
449 node: Node<'_>,
450 name: String,
451 arch: &'static ArchConfig,
452 source_line: u32,
453) -> Option<StructLayout> {
454 let mut is_packed = false;
455 let mut is_extern = false;
456 let mut raw_fields: Vec<(String, String, usize, usize, u32)> = Vec::new();
458 let mut uncertain_fields: Vec<String> = Vec::new();
459
460 for i in 0..node.child_count() {
461 let child = node.child(i)?;
462 match child.kind() {
463 "packed" => is_packed = true,
464 "extern" => is_extern = true,
465 "container_field" => {
466 if let Some(f) = parse_container_field(source, child, arch, is_packed) {
467 if is_zig_comptime_type(&f.1) {
468 uncertain_fields.push(f.0.clone());
469 }
470 raw_fields.push(f);
471 }
472 }
473 _ => {}
474 }
475 }
476
477 if raw_fields.is_empty() {
478 return None;
479 }
480
481 let mut offset = 0usize;
486 let mut struct_align = 1usize;
487 let mut fields: Vec<Field> = Vec::new();
488
489 if is_packed {
490 let mut bit_offset = 0usize;
494 for (fname, type_text, _byte_size, _byte_align, field_line) in &raw_fields {
495 let bit_width = zig_type_bit_width(type_text, arch);
496 let byte_offset = bit_offset / 8;
497 let byte_size = bit_width
498 .div_ceil(8)
499 .max(if bit_width == 0 { 0 } else { 1 });
500 fields.push(Field {
501 name: fname.clone(),
502 ty: TypeInfo::Primitive {
503 name: type_text.clone(),
504 size: byte_size,
505 align: 1,
506 },
507 offset: byte_offset,
508 size: byte_size,
509 align: 1,
510 source_file: None,
511 source_line: Some(*field_line),
512 access: padlock_core::ir::AccessPattern::Unknown,
513 });
514 bit_offset += bit_width;
515 }
516 offset = bit_offset.div_ceil(8);
517 struct_align = 1;
518 } else {
519 for (fname, type_text, size, align, field_line) in raw_fields {
520 if align > 0 {
521 offset = offset.next_multiple_of(align);
522 }
523 struct_align = struct_align.max(align);
524 fields.push(Field {
525 name: fname,
526 ty: TypeInfo::Primitive {
527 name: type_text,
528 size,
529 align,
530 },
531 offset,
532 size,
533 align,
534 source_file: None,
535 source_line: Some(field_line),
536 access: padlock_core::ir::AccessPattern::Unknown,
537 });
538 offset += size;
539 }
540 if struct_align > 0 {
541 offset = offset.next_multiple_of(struct_align);
542 }
543 }
544
545 let _ = is_extern; Some(StructLayout {
548 name,
549 total_size: offset,
550 align: struct_align,
551 fields,
552 source_file: None,
553 source_line: Some(source_line),
554 arch,
555 is_packed,
556 is_union: false,
557 is_repr_rust: false,
558 suppressed_findings: Vec::new(), uncertain_fields,
560 })
561}
562
563fn parse_container_field(
565 source: &str,
566 node: Node<'_>,
567 arch: &'static ArchConfig,
568 is_packed: bool,
569) -> Option<(String, String, usize, usize, u32)> {
570 let mut field_name: Option<String> = None;
571 let mut type_text: Option<String> = None;
572 let mut size_align: Option<(usize, usize)> = None;
573
574 for i in 0..node.child_count() {
575 let child = node.child(i)?;
576 match child.kind() {
577 "identifier" if field_name.is_none() => {
578 field_name = Some(source[child.byte_range()].to_string());
579 }
580 "builtin_type" | "pointer_type" | "nullable_type" | "slice_type" | "array_type"
581 | "error_union" => {
582 let text = source[child.byte_range()].to_string();
583 size_align = Some(type_node_size_align(source, child, arch));
584 type_text = Some(text);
585 }
586 "identifier" => {
587 let text = source[child.byte_range()].trim().to_string();
589 size_align = Some(zig_type_size_align(&text, arch));
590 type_text = Some(text);
591 }
592 _ => {}
593 }
594 }
595
596 let name = field_name.filter(|n| !n.is_empty())?;
599 let ty = type_text.unwrap_or_else(|| "anyopaque".to_string());
600 let (mut size, align) = size_align.unwrap_or((arch.pointer_size, arch.pointer_size));
601 let field_line = node.start_position().row as u32 + 1;
602
603 if is_packed && size == 0 {
604 size = 0; }
606
607 Some((name, ty, size, align, field_line))
608}
609
610pub fn parse_zig(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
613 let mut parser = Parser::new();
614 parser.set_language(&tree_sitter_zig::LANGUAGE.into())?;
615 let tree = parser
616 .parse(source, None)
617 .ok_or_else(|| anyhow::anyhow!("tree-sitter-zig parse failed"))?;
618 Ok(extract_structs(source, tree.root_node(), arch))
619}
620
621#[cfg(test)]
624mod tests {
625 use super::*;
626 use padlock_core::arch::X86_64_SYSV;
627
628 #[test]
629 fn parse_simple_zig_struct() {
630 let src = "const Point = struct { x: u32, y: u32 };";
631 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
632 assert_eq!(layouts.len(), 1);
633 assert_eq!(layouts[0].name, "Point");
634 assert_eq!(layouts[0].fields.len(), 2);
635 assert_eq!(layouts[0].total_size, 8);
636 }
637
638 #[test]
639 fn zig_layout_with_padding() {
640 let src = "const T = struct { a: bool, b: u64 };";
641 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
642 assert_eq!(layouts.len(), 1);
643 let l = &layouts[0];
644 assert_eq!(l.fields[0].offset, 0); assert_eq!(l.fields[1].offset, 8); assert_eq!(l.total_size, 16);
647 }
648
649 #[test]
650 fn zig_packed_struct_no_padding() {
651 let src = "const Packed = packed struct { a: u8, b: u32 };";
652 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
653 assert_eq!(layouts.len(), 1);
654 let l = &layouts[0];
655 assert!(l.is_packed);
656 assert_eq!(l.fields[0].offset, 0);
657 assert_eq!(l.fields[1].offset, 1); assert_eq!(l.total_size, 5);
659 }
660
661 #[test]
662 fn zig_extern_struct_detected() {
663 let src = "const Extern = extern struct { x: i32, y: f64 };";
664 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
665 assert_eq!(layouts.len(), 1);
666 let l = &layouts[0];
667 assert_eq!(l.fields[0].offset, 0);
669 assert_eq!(l.fields[1].offset, 8);
670 assert_eq!(l.total_size, 16);
671 }
672
673 #[test]
674 fn zig_pointer_field_is_pointer_sized() {
675 let src = "const S = struct { ptr: *u8 };";
676 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
677 assert_eq!(layouts[0].fields[0].size, 8);
678 assert_eq!(layouts[0].fields[0].align, 8);
679 }
680
681 #[test]
682 fn zig_optional_pointer_is_pointer_sized() {
683 let src = "const S = struct { opt: ?*u8 };";
684 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
685 assert_eq!(layouts[0].fields[0].size, 8);
686 }
687
688 #[test]
689 fn zig_slice_is_two_words() {
690 let src = "const S = struct { buf: []u8 };";
691 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
692 assert_eq!(layouts[0].fields[0].size, 16); }
694
695 #[test]
696 fn zig_usize_follows_arch() {
697 let src = "const S = struct { n: usize };";
698 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
699 assert_eq!(layouts[0].fields[0].size, 8);
700 }
701
702 #[test]
703 fn zig_multiple_structs_parsed() {
704 let src = "const A = struct { x: u8 };\nconst B = struct { y: u64 };";
705 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
706 assert_eq!(layouts.len(), 2);
707 assert!(layouts.iter().any(|l| l.name == "A"));
708 assert!(layouts.iter().any(|l| l.name == "B"));
709 }
710
711 #[test]
712 fn zig_array_field_size() {
713 let src = "const S = struct { buf: [4]u32 };";
714 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
715 assert_eq!(layouts[0].fields[0].size, 16); }
717
718 #[test]
721 fn zig_bare_union_parsed_as_union() {
722 let src = "const U = union { a: u8, b: u32 };";
723 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
724 assert_eq!(layouts.len(), 1);
725 let l = &layouts[0];
726 assert_eq!(l.name, "U");
727 assert!(l.is_union, "union should have is_union=true");
728 }
729
730 #[test]
731 fn zig_bare_union_total_size_is_max_field() {
732 let src = "const U = union { a: u8, b: u32 };";
734 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
735 let l = &layouts[0];
736 assert_eq!(l.total_size, 4);
737 }
738
739 #[test]
740 fn zig_union_all_fields_at_offset_zero() {
741 let src = "const U = union { a: u8, b: u64 };";
742 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
743 let l = &layouts[0];
744 for field in &l.fields {
745 assert_eq!(
746 field.offset, 0,
747 "union field '{}' should be at offset 0",
748 field.name
749 );
750 }
751 }
752
753 #[test]
754 fn zig_tagged_union_has_tag_field() {
755 let src = "const T = union(enum) { ok: u32, err: void };";
756 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
757 let l = &layouts[0];
758 assert!(
759 l.fields.iter().any(|f| f.name == "__tag"),
760 "tagged union should have a synthetic __tag field"
761 );
762 }
763
764 #[test]
765 fn zig_tagged_union_size_includes_tag() {
766 let src = "const T = union(enum) { ok: u32, err: void };";
769 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
770 let l = &layouts[0];
771 assert_eq!(l.total_size, 8);
773 }
774
775 #[test]
776 fn zig_union_with_largest_field_u64() {
777 let src = "const U = union { a: u8, b: u64, c: u32 };";
779 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
780 let l = &layouts[0];
781 assert_eq!(l.total_size, 8);
782 assert_eq!(l.align, 8);
783 }
784
785 #[test]
786 fn zig_struct_and_union_in_same_file() {
787 let src = "const S = struct { x: u32 };\nconst U = union { a: u8, b: u32 };";
788 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
789 assert_eq!(layouts.len(), 2);
790 assert!(layouts.iter().any(|l| l.name == "S" && !l.is_union));
791 assert!(layouts.iter().any(|l| l.name == "U" && l.is_union));
792 }
793
794 #[test]
797 fn zig_empty_union_returns_none() {
798 let src = "const E = union {};";
800 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
801 assert!(layouts.is_empty(), "empty union should produce no layout");
802 }
803
804 #[test]
805 fn zig_union_no_padding_finding() {
806 let src = "const U = union { a: u8, b: u64 };";
808 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
809 let gaps = padlock_core::ir::find_padding(&layouts[0]);
810 assert!(
811 gaps.is_empty(),
812 "unions should have no padding gaps: {:?}",
813 gaps
814 );
815 }
816
817 #[test]
820 fn zig_c_interop_types_correct_size() {
821 assert_eq!(zig_type_size_align("c_char", &X86_64_SYSV), (1, 1));
822 assert_eq!(zig_type_size_align("c_short", &X86_64_SYSV), (2, 2));
823 assert_eq!(zig_type_size_align("c_ushort", &X86_64_SYSV), (2, 2));
824 assert_eq!(zig_type_size_align("c_int", &X86_64_SYSV), (4, 4));
825 assert_eq!(zig_type_size_align("c_uint", &X86_64_SYSV), (4, 4));
826 assert_eq!(zig_type_size_align("c_long", &X86_64_SYSV), (8, 8));
828 assert_eq!(zig_type_size_align("c_ulong", &X86_64_SYSV), (8, 8));
829 assert_eq!(zig_type_size_align("c_longlong", &X86_64_SYSV), (8, 8));
830 assert_eq!(zig_type_size_align("c_ulonglong", &X86_64_SYSV), (8, 8));
831 assert_eq!(zig_type_size_align("c_float", &X86_64_SYSV), (4, 4));
832 assert_eq!(zig_type_size_align("c_double", &X86_64_SYSV), (8, 8));
833 assert_eq!(zig_type_size_align("c_longdouble", &X86_64_SYSV), (16, 16));
834 }
835
836 #[test]
837 fn zig_arbitrary_width_integers() {
838 assert_eq!(zig_type_size_align("u1", &X86_64_SYSV), (1, 1));
840 assert_eq!(zig_type_size_align("u3", &X86_64_SYSV), (1, 1));
842 assert_eq!(zig_type_size_align("u9", &X86_64_SYSV), (2, 2));
844 assert_eq!(zig_type_size_align("u24", &X86_64_SYSV), (3, 4));
846 assert_eq!(zig_type_size_align("u48", &X86_64_SYSV), (6, 8));
848 assert_eq!(zig_type_size_align("i7", &X86_64_SYSV), (1, 1));
850 assert_eq!(zig_type_size_align("u129", &X86_64_SYSV), (17, 8));
852 }
853
854 #[test]
855 fn zig_struct_with_c_interop_types() {
856 let src = "const Header = extern struct { version: c_uint, length: c_ushort, flags: u8 };";
858 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
859 assert_eq!(layouts.len(), 1);
860 let l = &layouts[0];
861 assert_eq!(l.fields[0].size, 4); assert_eq!(l.fields[1].size, 2); assert_eq!(l.fields[2].size, 1); }
865
866 #[test]
869 fn zig_comptime_type_field_is_flagged_uncertain() {
870 let src = "const Meta = struct { tag: type, value: u64 };";
872 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
873 let l = layouts.iter().find(|l| l.name == "Meta").expect("Meta");
874 assert!(
875 l.uncertain_fields.contains(&"tag".to_string()),
876 "comptime `type` field must be flagged as uncertain"
877 );
878 assert!(
879 !l.uncertain_fields.contains(&"value".to_string()),
880 "normal u64 field must not be flagged"
881 );
882 }
883
884 #[test]
885 fn zig_comptime_int_field_is_flagged_uncertain() {
886 let src = "const S = struct { n: comptime_int, x: u32 };";
887 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
888 let l = layouts.iter().find(|l| l.name == "S").expect("S");
889 assert!(l.uncertain_fields.contains(&"n".to_string()));
890 assert!(!l.uncertain_fields.contains(&"x".to_string()));
891 }
892
893 #[test]
894 fn zig_comptime_generic_fn_is_detected() {
895 crate::SKIPPED_COLLECTOR.with(|cell| cell.borrow_mut().clear());
896 let src =
897 "pub fn ArrayList(comptime T: type) type { return struct { items: []T, len: usize }; }";
898 let _layouts = parse_zig(src, &X86_64_SYSV).unwrap();
899 let skipped = crate::take_skipped();
900 assert!(
901 skipped.iter().any(|s| s.name == "ArrayList"),
902 "comptime-generic function must be recorded as skipped; got: {skipped:?}"
903 );
904 }
905}