ADR-0035: The code layer holds the types the analysed code references, as ArchUnitNET's ReferencedTypes
- Status: Accepted (2026-09-24, by the maintainer)
- Date: 2026-09-24
- Derives from: ADR-0004 (additions are additive), ADR-0009, ADR-0010, ADR-0011
- Constrains:
crates/rb-model/src/code.rs(TypeElement.referenced),crates/rb-extract-dotnet,crates/rb-rules/src/elements/,select.includeReferencedincrates/rb-config/src/elements.rs - Implemented by: Wave 2 plan, Step 5 and Step 7
- Requirements: FR-RULE-03, NFR-CONF-02
Context
ArchUnitNET keeps two sets of types. Architecture.Types are the loaded assemblies' own; ReferencedTypes are stubs for what they depend on, built by DomainResolver with the definition's kind, visibility and flags when Mono.Cecil can resolve the reference (from the loaded assembly's folder), else as an UnavailableType with only a name. Three things read the second set:
Types(true),Classes(true),Interfaces(true),Attributes(true)select from both;- a name or
typeofresolves against both (GetITypeOfTypesearchesAllTypes); - a relation condition with a nested predicate (
DependOnAnyTypesThat().Are(...),HaveAnyAttributesThat()) filters the actual dependency targets, which are often referenced types.
DependenciesToOtherAssembliesTests loads ArchUnitNETTests alone and asserts all three over types defined in TestAssembly. NetArchTest searches for dependencies on System namespaces, which only referenced types can answer.
Decision
TypeElementgainsreferenced: true(absent otherwise). The .NET extractor adds one referenced element for every dependency target the code layer does not define and that is not a generic parameter. It reads the assemblies beside the analysed ones that their references name, transitively, only to describe those types; when one defines the target, the element carries its kind, visibility, abstract, sealed, record, value type, nested and generic flags and its assembly names. Otherwise its kind isunavailable. A referenced element has no members, dependencies, file or attribution.- A selection leaves referenced types out unless it sets
select.includeReferenced: true. Names resolve against both sets. A selector nested in a relation condition sees both, asComplexConditionsees the targets.onlyDependOnstill judges only dependencies on the architecture's own types, and slices hold only them. - Framework assemblies from the .NET runtime directory are not read, so a
Systemtype isunavailablewhereArchUnitNETwould resolve it. No ported case depends on the difference, and reading the runtime would make the graph depend on the machine.
Consequences
DependenciesToOtherAssembliesTests(14 cases) and three NetArchTest searches are ported.- Graphs of .NET code are larger by one small element per external type referenced.
- TypeScript and Python extractors add no referenced elements; their external targets remain names that resolve but carry no facts.
Alternatives considered
- Build stubs in the engine from dependency targets. Rejected: the engine would know names but not kinds, so
Classes(true)could not be answered, and ADR-0010 keeps facts about code in the document. - Load the referenced assemblies as analysed ones (
includeDependencies). Rejected: their types would become rule objects, whichArchUnitNETdoes not do by default. - Read the runtime's reference assemblies. Deferred: it would make extraction depend on the installed SDK.