diff --git a/src/librustc/ty/sty.rs b/src/librustc/ty/sty.rs index 842e49dfc08..af447fc8a48 100644 --- a/src/librustc/ty/sty.rs +++ b/src/librustc/ty/sty.rs @@ -11,6 +11,7 @@ use rustc_macros::HashStable; use crate::ty::subst::{InternalSubsts, Subst, SubstsRef, Kind, UnpackedKind}; use crate::ty::{self, AdtDef, DefIdTree, TypeFlags, Ty, TyCtxt, TypeFoldable}; use crate::ty::{List, TyS, ParamEnvAnd, ParamEnv}; +use crate::ty::layout::VariantIdx; use crate::util::captures::Captures; use crate::mir::interpret::{Scalar, Pointer}; @@ -466,7 +467,44 @@ impl<'tcx> GeneratorSubsts<'tcx> { } impl<'a, 'gcx, 'tcx> GeneratorSubsts<'tcx> { + /// Generator have not been resumed yet + pub const UNRESUMED: usize = 0; + /// Generator has returned / is completed + pub const RETURNED: usize = 1; + /// Generator has been poisoned + pub const POISONED: usize = 2; + + const UNRESUMED_NAME: &'static str = "Unresumed"; + const RETURNED_NAME: &'static str = "Returned"; + const POISONED_NAME: &'static str = "Panicked"; + + /// The variants of this Generator. + #[inline] + pub fn variants(&self, def_id: DefId, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> + impl Iterator + { + // FIXME requires optimized MIR + let num_variants = self.state_tys(def_id, tcx).count(); + (0..num_variants).map(VariantIdx::new) + } + + /// Calls `f` with a reference to the name of the enumerator for the given + /// variant `v`. + #[inline] + pub fn map_variant_name(&self, v: VariantIdx, f: impl FnOnce(&str) -> R) -> R { + let name = match v.as_usize() { + Self::UNRESUMED => Self::UNRESUMED_NAME, + Self::RETURNED => Self::RETURNED_NAME, + Self::POISONED => Self::POISONED_NAME, + _ => { + return f(&format!("variant#{}", v.as_usize())); + } + }; + f(name) + } + /// The type of the state "discriminant" used in the generator type. + #[inline] pub fn discr_ty(&self, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> Ty<'tcx> { tcx.types.u32 } @@ -477,6 +515,7 @@ impl<'a, 'gcx, 'tcx> GeneratorSubsts<'tcx> { /// /// The locals are grouped by their variant number. Note that some locals may /// be repeated in multiple variants. + #[inline] pub fn state_tys(self, def_id: DefId, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> impl Iterator> + Captures<'gcx> + 'a> { @@ -487,6 +526,7 @@ impl<'a, 'gcx, 'tcx> GeneratorSubsts<'tcx> { /// This is the types of the fields of a generator which are not stored in a /// variant. + #[inline] pub fn prefix_tys(self, def_id: DefId, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> impl Iterator> + 'a { diff --git a/src/librustc_codegen_llvm/debuginfo/metadata.rs b/src/librustc_codegen_llvm/debuginfo/metadata.rs index bbcd3c220d6..0b1ac1ad788 100644 --- a/src/librustc_codegen_llvm/debuginfo/metadata.rs +++ b/src/librustc_codegen_llvm/debuginfo/metadata.rs @@ -28,7 +28,7 @@ use rustc_data_structures::fingerprint::Fingerprint; use rustc::ty::Instance; use rustc::ty::{self, AdtKind, ParamEnv, Ty, TyCtxt}; use rustc::ty::layout::{self, Align, Integer, IntegerExt, LayoutOf, - PrimitiveExt, Size, TyLayout}; + PrimitiveExt, Size, TyLayout, VariantIdx}; use rustc::ty::subst::UnpackedKind; use rustc::session::config; use rustc::util::nodemap::FxHashMap; @@ -691,17 +691,15 @@ pub fn type_metadata( usage_site_span).finalize(cx) } ty::Generator(def_id, substs, _) => { - // TODO handle variant fields let upvar_tys : Vec<_> = substs.prefix_tys(def_id, cx.tcx).map(|t| { cx.tcx.normalize_erasing_regions(ParamEnv::reveal_all(), t) }).collect(); - // TODO use prepare_enum_metadata and update it to handle multiple - // fields in the outer layout. - prepare_tuple_metadata(cx, - t, - &upvar_tys, - unique_type_id, - usage_site_span).finalize(cx) + prepare_enum_metadata(cx, + t, + def_id, + unique_type_id, + usage_site_span, + upvar_tys).finalize(cx) } ty::Adt(def, ..) => match def.adt_kind() { AdtKind::Struct => { @@ -721,7 +719,8 @@ pub fn type_metadata( t, def.did, unique_type_id, - usage_site_span).finalize(cx) + usage_site_span, + vec![]).finalize(cx) } }, ty::Tuple(ref elements) => { @@ -998,6 +997,31 @@ struct MemberDescription<'ll> { discriminant: Option, } +impl<'ll> MemberDescription<'ll> { + fn into_metadata(self, + cx: &CodegenCx<'ll, '_>, + composite_type_metadata: &'ll DIScope) -> &'ll DIType { + let member_name = CString::new(self.name).unwrap(); + unsafe { + llvm::LLVMRustDIBuilderCreateVariantMemberType( + DIB(cx), + composite_type_metadata, + member_name.as_ptr(), + unknown_file_metadata(cx), + UNKNOWN_LINE_NUMBER, + self.size.bits(), + self.align.bits() as u32, + self.offset.bits(), + match self.discriminant { + None => None, + Some(value) => Some(cx.const_u64(value)), + }, + self.flags, + self.type_metadata) + } + } +} + // A factory for MemberDescriptions. It produces a list of member descriptions // for some record-like type. MemberDescriptionFactories are used to defer the // creation of type member descriptions in order to break cycles arising from @@ -1264,7 +1288,13 @@ struct EnumMemberDescriptionFactory<'ll, 'tcx> { impl EnumMemberDescriptionFactory<'ll, 'tcx> { fn create_member_descriptions(&self, cx: &CodegenCx<'ll, 'tcx>) -> Vec> { - let adt = &self.enum_type.ty_adt_def().unwrap(); + let variant_info_for = |index: VariantIdx| { + match &self.enum_type.sty { + ty::Adt(adt, _) => VariantInfo::Adt(&adt.variants[index]), + ty::Generator(_, substs, _) => VariantInfo::Generator(*substs, index), + _ => bug!(), + } + }; // This will always find the metadata in the type map. let fallback = use_enum_fallback(cx); @@ -1275,12 +1305,18 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { }; match self.layout.variants { - layout::Variants::Single { .. } if adt.variants.is_empty() => vec![], layout::Variants::Single { index } => { + if let ty::Adt(adt, _) = &self.enum_type.sty { + if adt.variants.is_empty() { + return vec![]; + } + } + + let variant_info = variant_info_for(index); let (variant_type_metadata, member_description_factory) = describe_enum_variant(cx, self.layout, - &adt.variants[index], + variant_info, NoDiscriminant, self_metadata, self.span); @@ -1297,7 +1333,7 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { name: if fallback { String::new() } else { - adt.variants[index].ident.as_str().to_string() + variant_info.name_as_string() }, type_metadata: variant_type_metadata, offset: Size::ZERO, @@ -1325,10 +1361,11 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { }; variants.iter_enumerated().map(|(i, _)| { let variant = self.layout.for_variant(cx, i); + let variant_info = variant_info_for(i); let (variant_type_metadata, member_desc_factory) = describe_enum_variant(cx, variant, - &adt.variants[i], + variant_info, discriminant_info, self_metadata, self.span); @@ -1340,20 +1377,25 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { self.enum_type, variant_type_metadata, member_descriptions); + + // TODO make this into a helper + let discriminant = match &self.layout.ty.sty { + ty::Adt(adt, _) => adt.discriminant_for_variant(cx.tcx, i).val as u64, + ty::Generator(..) => i.as_usize() as u64, + _ => bug!(), + }.into(); MemberDescription { name: if fallback { String::new() } else { - adt.variants[i].ident.as_str().to_string() + variant_info.name_as_string() }, type_metadata: variant_type_metadata, offset: Size::ZERO, size: self.layout.size, align: self.layout.align.abi, flags: DIFlags::FlagZero, - discriminant: Some(self.layout.ty.ty_adt_def().unwrap() - .discriminant_for_variant(cx.tcx, i) - .val as u64), + discriminant, } }).collect() } @@ -1373,7 +1415,7 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { let (variant_type_metadata, member_description_factory) = describe_enum_variant(cx, variant, - &adt.variants[dataful_variant], + variant_info_for(dataful_variant), OptimizedDiscriminant, self.containing_scope, self.span); @@ -1413,7 +1455,9 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { self.layout, self.layout.fields.offset(discr_index), self.layout.field(cx, discr_index).size); - name.push_str(&adt.variants[*niche_variants.start()].ident.as_str()); + variant_info_for(*niche_variants.start()).map_name(|variant_name| { + name.push_str(variant_name); + }); // Create the (singleton) list of descriptions of union members. vec![ @@ -1430,10 +1474,11 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { } else { variants.iter_enumerated().map(|(i, _)| { let variant = self.layout.for_variant(cx, i); + let variant_info = variant_info_for(i); let (variant_type_metadata, member_desc_factory) = describe_enum_variant(cx, variant, - &adt.variants[i], + variant_info, OptimizedDiscriminant, self_metadata, self.span); @@ -1461,7 +1506,7 @@ impl EnumMemberDescriptionFactory<'ll, 'tcx> { }; MemberDescription { - name: adt.variants[i].ident.as_str().to_string(), + name: variant_info.name_as_string(), type_metadata: variant_type_metadata, offset: Size::ZERO, size: self.layout.size, @@ -1519,6 +1564,34 @@ enum EnumDiscriminantInfo<'ll> { NoDiscriminant } +#[derive(Copy, Clone)] +enum VariantInfo<'tcx> { + Adt(&'tcx ty::VariantDef), + Generator(ty::GeneratorSubsts<'tcx>, VariantIdx), +} + +impl<'tcx> VariantInfo<'tcx> { + fn map_name(&self, f: impl FnOnce(&str) -> R) -> R { + match self { + VariantInfo::Adt(variant) => f(&variant.ident.as_str()), + VariantInfo::Generator(substs, variant_index) => + substs.map_variant_name(*variant_index, f), + } + } + + fn name_as_string(&self) -> String { + self.map_name(|name| name.to_string()) + } + + fn field_name(&self, i: usize) -> String { + match self { + VariantInfo::Adt(variant) if variant.ctor_kind != CtorKind::Fn => + variant.fields[i].ident.to_string(), + _ => format!("__{}", i), + } + } +} + // Returns a tuple of (1) type_metadata_stub of the variant, (2) a // MemberDescriptionFactory for producing the descriptions of the // fields of the variant. This is a rudimentary version of a full @@ -1526,32 +1599,24 @@ enum EnumDiscriminantInfo<'ll> { fn describe_enum_variant( cx: &CodegenCx<'ll, 'tcx>, layout: layout::TyLayout<'tcx>, - variant: &'tcx ty::VariantDef, + variant: VariantInfo<'tcx>, discriminant_info: EnumDiscriminantInfo<'ll>, containing_scope: &'ll DIScope, span: Span, ) -> (&'ll DICompositeType, MemberDescriptionFactory<'ll, 'tcx>) { - let variant_name = variant.ident.as_str(); - let unique_type_id = debug_context(cx).type_map - .borrow_mut() - .get_unique_type_id_of_enum_variant( - cx, - layout.ty, - &variant_name); - - let metadata_stub = create_struct_stub(cx, - layout.ty, - &variant_name, - unique_type_id, - Some(containing_scope)); - - let arg_name = |i: usize| { - if variant.ctor_kind == CtorKind::Fn { - format!("__{}", i) - } else { - variant.fields[i].ident.to_string() - } - }; + let metadata_stub = variant.map_name(|variant_name| { + let unique_type_id = debug_context(cx).type_map + .borrow_mut() + .get_unique_type_id_of_enum_variant( + cx, + layout.ty, + &variant_name); + create_struct_stub(cx, + layout.ty, + &variant_name, + unique_type_id, + Some(containing_scope)) + }); // Build an array of (field name, field type) pairs to be captured in the factory closure. let (offsets, args) = if use_enum_fallback(cx) { @@ -1573,7 +1638,7 @@ fn describe_enum_variant( layout.fields.offset(i) })).collect(), discr_arg.into_iter().chain((0..layout.fields.count()).map(|i| { - (arg_name(i), layout.field(cx, i).ty) + (variant.field_name(i), layout.field(cx, i).ty) })).collect() ) } else { @@ -1582,7 +1647,7 @@ fn describe_enum_variant( layout.fields.offset(i) }).collect(), (0..layout.fields.count()).map(|i| { - (arg_name(i), layout.field(cx, i).ty) + (variant.field_name(i), layout.field(cx, i).ty) }).collect() ) }; @@ -1609,6 +1674,7 @@ fn prepare_enum_metadata( enum_def_id: DefId, unique_type_id: UniqueTypeId, span: Span, + outer_field_tys: Vec>, ) -> RecursiveTypeDescription<'ll, 'tcx> { let enum_name = compute_debuginfo_type_name(cx.tcx, enum_type, false); @@ -1622,20 +1688,36 @@ fn prepare_enum_metadata( let file_metadata = unknown_file_metadata(cx); let discriminant_type_metadata = |discr: layout::Primitive| { - let def = enum_type.ty_adt_def().unwrap(); - let enumerators_metadata: Vec<_> = def.discriminants(cx.tcx) - .zip(&def.variants) - .map(|((_, discr), v)| { - let name = SmallCStr::new(&v.ident.as_str()); - unsafe { - Some(llvm::LLVMRustDIBuilderCreateEnumerator( - DIB(cx), - name.as_ptr(), - // FIXME: what if enumeration has i128 discriminant? - discr.val as u64)) - } - }) - .collect(); + let enumerators_metadata: Vec<_> = match enum_type.sty { + ty::Adt(def, _) => def + .discriminants(cx.tcx) + .zip(&def.variants) + .map(|((_, discr), v)| { + let name = SmallCStr::new(&v.ident.as_str()); + unsafe { + Some(llvm::LLVMRustDIBuilderCreateEnumerator( + DIB(cx), + name.as_ptr(), + // FIXME: what if enumeration has i128 discriminant? + discr.val as u64)) + } + }) + .collect(), + ty::Generator(_, substs, _) => substs + .variants(enum_def_id, cx.tcx) + .map(|v| substs.map_variant_name(v, |name| { + let name = SmallCStr::new(name); + unsafe { + Some(llvm::LLVMRustDIBuilderCreateEnumerator( + DIB(cx), + name.as_ptr(), + // FIXME: what if enumeration has i128 discriminant? + v.as_usize() as u64)) + } + })) + .collect(), + _ => bug!(), + }; let disr_type_key = (enum_def_id, discr); let cached_discriminant_type_metadata = debug_context(cx).created_enum_disr_types @@ -1648,14 +1730,18 @@ fn prepare_enum_metadata( (discr.size(cx), discr.align(cx)); let discriminant_base_type_metadata = type_metadata(cx, discr.to_ty(cx.tcx), syntax_pos::DUMMY_SP); - let discriminant_name = get_enum_discriminant_name(cx, enum_def_id).as_str(); - let name = SmallCStr::new(&discriminant_name); + let discriminant_name = match enum_type.sty { + ty::Adt(..) => SmallCStr::new(&cx.tcx.item_name(enum_def_id).as_str()), + ty::Generator(..) => SmallCStr::new(&enum_name), + _ => bug!(), + }; + let discriminant_type_metadata = unsafe { llvm::LLVMRustDIBuilderCreateEnumerationType( DIB(cx), containing_scope, - name.as_ptr(), + discriminant_name.as_ptr(), file_metadata, UNKNOWN_LINE_NUMBER, discriminant_size.bits(), @@ -1736,6 +1822,11 @@ fn prepare_enum_metadata( ); } + let discriminator_name = match &enum_type.sty { + ty::Generator(..) => Some(SmallCStr::new(&"__state")), + _ => None, + }; + let discriminator_name = discriminator_name.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()); let discriminator_metadata = match layout.variants { // A single-variant enum has no discriminant. layout::Variants::Single { .. } => None, @@ -1762,7 +1853,7 @@ fn prepare_enum_metadata( Some(llvm::LLVMRustDIBuilderCreateMemberType( DIB(cx), containing_scope, - ptr::null_mut(), + discriminator_name, file_metadata, UNKNOWN_LINE_NUMBER, size.bits(), @@ -1787,7 +1878,7 @@ fn prepare_enum_metadata( Some(llvm::LLVMRustDIBuilderCreateMemberType( DIB(cx), containing_scope, - ptr::null_mut(), + discriminator_name, file_metadata, UNKNOWN_LINE_NUMBER, size.bits(), @@ -1799,6 +1890,22 @@ fn prepare_enum_metadata( }, }; + let mut outer_fields = match layout.variants { + layout::Variants::Single { .. } => vec![], + layout::Variants::Multiple { .. } => { + let tuple_mdf = TupleMemberDescriptionFactory { + ty: enum_type, + component_types: outer_field_tys, + span + }; + tuple_mdf + .create_member_descriptions(cx) + .into_iter() + .map(|desc| Some(desc.into_metadata(cx, containing_scope))) + .collect() + } + }; + let variant_part_unique_type_id_str = SmallCStr::new( debug_context(cx).type_map .borrow_mut() @@ -1819,10 +1926,10 @@ fn prepare_enum_metadata( empty_array, variant_part_unique_type_id_str.as_ptr()) }; + outer_fields.push(Some(variant_part)); // The variant part must be wrapped in a struct according to DWARF. - // TODO create remaining fields here, if any. - let type_array = create_DIArray(DIB(cx), &[Some(variant_part)]); + let type_array = create_DIArray(DIB(cx), &outer_fields); let struct_wrapper = unsafe { llvm::LLVMRustDIBuilderCreateStructType( DIB(cx), @@ -1854,12 +1961,6 @@ fn prepare_enum_metadata( span, }), ); - - fn get_enum_discriminant_name(cx: &CodegenCx<'_, '_>, - def_id: DefId) - -> InternedString { - cx.tcx.item_name(def_id) - } } /// Creates debug information for a composite type, that is, anything that @@ -1917,26 +2018,7 @@ fn set_members_of_composite_type(cx: &CodegenCx<'ll, 'tcx>, let member_metadata: Vec<_> = member_descriptions .into_iter() - .map(|member_description| { - let member_name = CString::new(member_description.name).unwrap(); - unsafe { - Some(llvm::LLVMRustDIBuilderCreateVariantMemberType( - DIB(cx), - composite_type_metadata, - member_name.as_ptr(), - unknown_file_metadata(cx), - UNKNOWN_LINE_NUMBER, - member_description.size.bits(), - member_description.align.bits() as u32, - member_description.offset.bits(), - match member_description.discriminant { - None => None, - Some(value) => Some(cx.const_u64(value)), - }, - member_description.flags, - member_description.type_metadata)) - } - }) + .map(|desc| Some(desc.into_metadata(cx, composite_type_metadata))) .collect(); let type_params = compute_type_parameters(cx, composite_type); diff --git a/src/librustc_mir/transform/generator.rs b/src/librustc_mir/transform/generator.rs index b7c4bfd5126..36db8c0b7ef 100644 --- a/src/librustc_mir/transform/generator.rs +++ b/src/librustc_mir/transform/generator.rs @@ -54,6 +54,7 @@ use rustc::hir::def_id::DefId; use rustc::mir::*; use rustc::mir::visit::{PlaceContext, Visitor, MutVisitor}; use rustc::ty::{self, TyCtxt, AdtDef, Ty}; +use rustc::ty::GeneratorSubsts; use rustc::ty::layout::VariantIdx; use rustc::ty::subst::SubstsRef; use rustc_data_structures::fx::FxHashMap; @@ -145,11 +146,11 @@ fn self_arg() -> Local { } /// Generator have not been resumed yet -const UNRESUMED: usize = 0; +const UNRESUMED: usize = GeneratorSubsts::UNRESUMED; /// Generator has returned / is completed -const RETURNED: usize = 1; +const RETURNED: usize = GeneratorSubsts::RETURNED; /// Generator has been poisoned -const POISONED: usize = 2; +const POISONED: usize = GeneratorSubsts::POISONED; struct SuspensionPoint { state: usize, diff --git a/src/test/debuginfo/generators.rs b/src/test/debuginfo/generator-locals.rs similarity index 100% rename from src/test/debuginfo/generators.rs rename to src/test/debuginfo/generator-locals.rs diff --git a/src/test/debuginfo/generator-objects.rs b/src/test/debuginfo/generator-objects.rs new file mode 100644 index 00000000000..a7c1ac1e902 --- /dev/null +++ b/src/test/debuginfo/generator-objects.rs @@ -0,0 +1,68 @@ +// ignore-tidy-linelength + +// Require LLVM with DW_TAG_variant_part and a gdb that can read it. +// min-system-llvm-version: 8.0 +// min-gdb-version: 8.2 + +// compile-flags:-g + +// === GDB TESTS =================================================================================== + +// gdb-command:run +// gdb-command:print b +// gdb-check:$1 = generator_objects::main::generator {__0: 0x[...], <>: {__state: 0, Unresumed: generator_objects::main::generator::Unresumed, Returned: generator_objects::main::generator::Returned, Panicked: generator_objects::main::generator::Panicked, variant#3: generator_objects::main::generator::variant#3 ([...]), variant#4: generator_objects::main::generator::variant#4 ([...])}} +// gdb-command:continue +// gdb-command:print b +// gdb-check:$2 = generator_objects::main::generator {__0: 0x[...], <>: {__state: 3, Unresumed: generator_objects::main::generator::Unresumed, Returned: generator_objects::main::generator::Returned, Panicked: generator_objects::main::generator::Panicked, variant#3: generator_objects::main::generator::variant#3 (6, 7), variant#4: generator_objects::main::generator::variant#4 ([...])}} +// gdb-command:continue +// gdb-command:print b +// gdb-check:$3 = generator_objects::main::generator {__0: 0x[...], <>: {__state: 4, Unresumed: generator_objects::main::generator::Unresumed, Returned: generator_objects::main::generator::Returned, Panicked: generator_objects::main::generator::Panicked, variant#3: generator_objects::main::generator::variant#3 ([...]), variant#4: generator_objects::main::generator::variant#4 (7, 8)}} +// gdb-command:continue +// gdb-command:print b +// gdb-check:$4 = generator_objects::main::generator {__0: 0x[...], <>: {__state: 1, Unresumed: generator_objects::main::generator::Unresumed, Returned: generator_objects::main::generator::Returned, Panicked: generator_objects::main::generator::Panicked, variant#3: generator_objects::main::generator::variant#3 ([...]), variant#4: generator_objects::main::generator::variant#4 ([...])}} + +// === LLDB TESTS ================================================================================== + +// lldb-command:run +// lldb-command:print b +// lldbg-check:(generator_objects::main::generator) $0 = generator(&0x[...]) +// lldb-command:continue +// lldb-command:print b +// lldbg-check:(generator_objects::main::generator) $1 = generator(&0x[...]) +// lldb-command:continue +// lldb-command:print b +// lldbg-check:(generator_objects::main::generator) $2 = generator(&0x[...]) +// lldb-command:continue +// lldb-command:print b +// lldbg-check:(generator_objects::main::generator) $3 = generator(&0x[...]) + +#![feature(omit_gdb_pretty_printer_section, generators, generator_trait)] +#![omit_gdb_pretty_printer_section] + +use std::ops::Generator; +use std::pin::Pin; + +fn main() { + let mut a = 5; + let mut b = || { + let mut c = 6; + let mut d = 7; + + yield; + a += 1; + c += 1; + d += 1; + + yield; + println!("{} {} {}", a, c, d); + }; + _zzz(); // #break + Pin::new(&mut b).resume(); + _zzz(); // #break + Pin::new(&mut b).resume(); + _zzz(); // #break + Pin::new(&mut b).resume(); + _zzz(); // #break +} + +fn _zzz() {()}