skip to content

A field-walking renderer emits a column no one declared in the source — where did that member come from?

level: seniorimportance: should knowfreq 38%

answer

  1. compiled output, not source text
  2. the compiler adds its own members
  3. an enclosing link, captured values
  4. a forwarder beside the real override
  5. filter on the generated flag

basics

~20 s

From the compiler. A type description describes compiled output, which carries members nobody wrote: a link to an enclosing instance, slots for captured values, forwarding members that make an override line up, and members added by build-time or load-time instrumentation.

solid answer

~50 s

The description is of the compiled type, not of the source file, and compilers add members of their own. A type declared inside another and tied to an enclosing instance gets a hidden field holding that instance. A type defined inside a method gets one field per captured value. Where a runtime discards type arguments after checking them, the compiler emits a forwarding member with the general signature that delegates to the specific one, so a method appears twice with different result types. Instrumentation applied during the build or at load time adds its own fields. Most descriptions mark these with a **generated** flag, and that flag is what a walker should filter on — filtering by a name prefix is a guess about one toolchain's naming habit, while filtering by visibility removes the wrong things entirely.

code

pseudocode · 8 lines
pseudocode
function dataFields(type)
    result = []
    for each field in declaredFieldsOf(type)
        if field.isCompilerGenerated then continue   // enclosing link, captured value
        if field.belongsToType then continue          // shared by the type, not per row
        if field.isNestedType then continue
        result.append(field)
    return result

go deeper

for a junior

Know that a type's description can list members nobody typed, and that anything walking members needs to filter rather than trust the list wholesale.

for a middle

Name the sources of generated members — enclosing links, captured values, forwarders, instrumentation — and explain why the description reflects compiled output rather than the source.

for a senior

Diagnose the live symptoms: the phantom column, the duplicate method with two result types, the mapper writing into a hidden slot, and defend filtering on flags as an allow-list instead of on names.

for a principal

Set the expectation that any component walking types in your systems ships with an allow-list, because the failure mode is silent and each new toolchain or rewriter in the build can add a member kind nobody anticipated.

## The description describes compiled output A type description is built from what the runtime loaded, and what it loaded is the compiler's output. That output is not a transcript of the source. Things the author wrote can be missing — parameter names are often dropped unless the build keeps them — and things the author never wrote are present, because the compiler needed them to implement what the source said. For a renderer that turns every field into a column, the visible symptom is a phantom column: a header nobody recognises, holding something that is not data, sometimes the whole enclosing object. ## Where generated members come from - **A link to the enclosing instance.** A type declared inside another and tied to an instance of it must reach that instance somehow; the compiler stores it in a hidden field. - **Captured values.** A type defined inside a method body that uses surrounding variables gets one hidden field per captured value, written at construction. - **Forwarding members.** Where a runtime discards type arguments after checking them, an override written against a specific type does not match the general signature callers use; the compiler emits an extra member with the general signature that delegates to the real one. - **Members backing a fixed set of constants.** A type whose instances are a closed, named set usually carries a generated collection of them plus generated lookup methods. - **Access helpers across a nesting boundary.** Where an inner declaration reaches a hidden member of its enclosing type, the compiler may add a small helper member that performs the access. - **Instrumentation.** Tooling that rewrites compiled code during the build, or as it is loaded, adds fields and methods of its own; the description reports these exactly like the rest. ## Telling them apart | signal | what it means | how far to trust it | |---|---|---| | a generated flag on the member | the compiler or a rewriter produced it | the reliable signal — use it first | | belongs to the type, not the instance | not per-row data whatever produced it | reliable for excluding from a row renderer | | a marker character in the name | one toolchain's convention | a guess; conventions differ and can change | | a method with a duplicate name and a wider result type | probably a forwarder to the real one | strong hint, worth confirming with the flag | The order matters. Filter on the generated flag, then on whether the member belongs to the type rather than the instance, and only then consider names. A visibility filter does not help at all here — generated members may be visible or hidden, and hiding is not what marks them. ## What happens when nothing filters them 1. The report grows a column whose values are an enclosing object rendered as text, or a captured value that had nothing to do with the record. 2. A mapper built the same way writes into one of those hidden slots and breaks an invariant the compiler was relying on — the object then misbehaves far from the write. 3. A method inventory shows what looks like a duplicate: one name, the same parameters, two different result types. Treating that as corruption is the classic misreading; it is a forwarder beside the member it forwards to. 4. Serialising the walk's output drags the enclosing instance along with it, so a small record produces a large document. ## Nested types are members too Some descriptions expose a type's nested types through the same member listing that carries fields and methods. A field walker that does not exclude them emits a column for a type. That is a separate class from compiler-generated members — the nested type *was* declared in the source — but the fix belongs in the same filtering step, because both are cases of the description carrying more kinds of thing than the walker assumed. ## The rule that generalises Treat the description as the compiled truth, and treat the source as a story about it. Anything a walker hands onward should have survived an explicit allow-list: an instance member, not compiler-generated, not a nested type, with a declared type the renderer can format. Writing the filter as an allow-list rather than a deny-list means the next unfamiliar kind of member is dropped rather than rendered — which is the behaviour you want from a component whose whole purpose is handling types it has never seen.

  • A walker sees two methods with one name, the same parameters and different result types — what is that?
    A forwarding member beside the real one. Where a runtime discards type arguments after checking them, an override written against a specific type does not match the general signature callers use, so the compiler emits a member with the general signature that delegates to the specific one. Both are in the description. Keep the declared one and drop the member carrying the generated flag, or you will report the same operation twice.
  • A type is defined inside a method and captures a local variable. What does its description show?
    An extra field for each captured value, marked generated and usually not writable, holding a copy taken when the instance was created. If the type is also tied to an enclosing instance, a further hidden field holds that. A field walker reports all of them as columns, and a mapper that writes to them corrupts state the compiler assumed nothing outside would touch.

saying these in an interview costs you the question

  • Assumes every member in the description appears in the source.
  • Filters generated members by a name prefix rather than a flag.
  • Calls two same-named methods with different result types corruption.
  • Ignores that nested types can appear in the same member listing.
  • Treats a hidden enclosing-instance link as ordinary data to render.