Skip to content

Source generator: support source-generating compiled companion NodeSets - #4062

Closed
marcschier wants to merge 2 commits into
masterfrom
marcschier/sourcegen-companion-models
Closed

Source generator: support source-generating compiled companion NodeSets#4062
marcschier wants to merge 2 commits into
masterfrom
marcschier/sourcegen-companion-models

Conversation

@marcschier

@marcschier marcschier commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What

Enhancements to the model source generator so that a compiled companion NodeSet2 (an OPC companion spec such as Robotics/IA, or an application companion model such as an OpenUSD binding) can be source-generated:

  • Add OpcUaStateTypeIndex and wire it through ModelCompilation / ModelSourceGenerator so standard (ns=0) node-state / method-state class names are resolved from the referenced Opc.Ua.Core(.Types) metadata during the model pass.
  • Add StandardMethodStateFallback: when a compiled NodeSet instantiates a standard type that carries methods (e.g. a FileDirectoryType object whose Delete method is modelled as a plain instance method), the default naming can compute a global::Opc.Ua.*MethodState class name that Opc.Ua.Core.Types never emits under that name (Core emits the curated name derived from the method type, e.g. DeleteFileMethodState). This degrades an unresolved standard global::Opc.Ua.*MethodState reference to the base global::Opc.Ua.MethodStateonly when that typed class is neither declared by the current pass nor present in the compilation. It is keyed on the exact Opc.Ua namespace, records declared method-state classes at their declaration site (so a model that legitimately emits its own Opc.Ua.*MethodState, e.g. GDS, is never degraded), and the Stack pass that builds Opc.Ua.Core.Types never enters the scope.
  • Supporting changes in NodeStateGenerator, ModelDesignExtensions, and FluentBuilderGenerator.
  • Opc.Ua.Types: expose internals to Opc.Ua.Core.Encoders.Tests.

Why

Today a companion model that (a) is consumed as a compiled NodeSet2 and (b) instantiates a standard type that carries methods or state-machine members cannot be source-generated without this fallback. This is the scoped, model-generation-only interim workaround tracked by #4060; the proper long-term fix is an upstream name-resolution step in the generator (also described in #4060), which would make the fallback unnecessary.

Validation

  • Generator builds (netstandard2.0).
  • Opc.Ua.Core.Types generates and builds.
  • Full Opc.Ua.SourceGeneration.Core test suite passes: 3718 passed / 8 skipped / 0 failed.
  • Full Opc.Ua.SourceGeneration test suite passes: 68 passed / 0 skipped / 0 failed.

Context

This is the generic, upstream-worthy prerequisite extracted from a larger OpenUSD/Robotics integration. The full integration lives on the stacked branch marcschier/openusd-integration (this branch is a clean prefix of it) and is not part of this PR.

Enhancements to the model source generator so that a *compiled* companion
NodeSet2 (e.g. an OPC companion spec such as Robotics/IA, or the OpenUSD
binding model) can be source-generated:

- Add OpcUaStateTypeIndex and wire it through ModelCompilation /
  ModelSourceGenerator so standard (ns=0) node-state/method-state class
  names are resolved from the referenced Core(.Types) metadata.
- Add StandardMethodStateFallback: when a compiled NodeSet instantiates a
  standard type that carries methods (e.g. a FileDirectoryType 'Delete'
  instance), the default naming can compute an 'Opc.Ua.*MethodState' name
  that Core never emits under that name. Degrade an unresolved standard
  'global::Opc.Ua.*MethodState' reference to the base 'global::Opc.Ua.
  MethodState' only when that typed class is neither declared by the
  current pass nor present in the compilation, keyed on the exact Opc.Ua
  namespace and never entered by the Stack pass that builds Core.Types.
  This is the scoped interim workaround tracked by
  #4060 (the proper upstream fix is a
  name-resolution step, which this makes unnecessary once landed).
- NodeStateGenerator / ModelDesignExtensions / FluentBuilderGenerator:
  supporting changes for the above.
- Opc.Ua.Types: expose internals to Opc.Ua.Core.Encoders.Tests.

Validated: generator builds (netstandard2.0), Opc.Ua.Core.Types generates
and builds, and the full Opc.Ua.SourceGeneration.Core test suite passes
(3715 passed / 8 skipped / 0 failed).
Copilot AI review requested due to automatic review settings July 20, 2026 16:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Enhances the OPC UA model source generator to support source-generating from compiled companion NodeSet2 inputs by resolving standard (ns=0) state/method-state types from referenced assemblies and introducing a scoped fallback when curated Core assemblies omit certain typed *MethodState classes.

Changes:

  • Add an OpcUaStateTypeIndex built from Roslyn Compilation metadata and plumb it through ModelSourceGeneratorModelCompilation.
  • Introduce StandardMethodStateFallback (scoped to model generation) and apply it in ModelDesignExtensions, while recording locally-declared standard-namespace method-state classes.
  • Fix XML doc generation in FluentBuilderGenerator by escaping browse names; expose internals to Opc.Ua.Core.Encoders.Tests.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tools/Opc.Ua.SourceGeneration/OpcUaStateTypeIndex.cs New compilation-based index of available Opc.Ua.*State types to support fallback decisions.
tools/Opc.Ua.SourceGeneration/ModelSourceGenerator.cs Wires the state-type index into the incremental generator pipeline.
tools/Opc.Ua.SourceGeneration/ModelCompilation.cs Enters the fallback scope during model-generation passes.
tools/Opc.Ua.SourceGeneration.Core/Schema/StandardMethodStateFallback.cs Adds scoped policy to degrade missing standard typed *MethodState references to MethodState.
tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesignExtensions.cs Applies the fallback when emitting typed method-state references.
tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateGenerator.cs Records declared method-state classes to prevent incorrect degradation on later references.
tools/Opc.Ua.SourceGeneration.Core/Generators/FluentBuilderGenerator.cs Escapes XML doc content to prevent malformed XML (CS1570).
src/Opc.Ua.Types/Opc.Ua.Types.csproj Adds InternalsVisibleTo for encoder tests.

Comment thread tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateGenerator.cs
Comment thread tools/Opc.Ua.SourceGeneration.Core/Schema/StandardMethodStateFallback.cs Outdated
Comment thread tools/Opc.Ua.SourceGeneration/ModelSourceGenerator.cs Outdated
Comment thread tools/Opc.Ua.SourceGeneration.Core/Schema/StandardMethodStateFallback.cs Outdated
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.16216% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.57%. Comparing base (12f770b) to head (87778b7).
⚠️ Report is 13 commits behind head on master.

Files with missing lines Patch % Lines
...eration.Core/Schema/StandardMethodStateFallback.cs 77.50% 4 Missing and 5 partials ⚠️
...ols/Opc.Ua.SourceGeneration/OpcUaStateTypeIndex.cs 71.42% 2 Missing and 4 partials ⚠️
...neration.Core/Generators/FluentBuilderGenerator.cs 76.92% 2 Missing and 1 partial ⚠️
...rceGeneration.Core/Schema/ModelDesignExtensions.cs 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4062      +/-   ##
==========================================
- Coverage   73.86%   73.57%   -0.30%     
==========================================
  Files        1345     1347       +2     
  Lines      180038   180201     +163     
  Branches    31678    31704      +26     
==========================================
- Hits       132993   132577     -416     
- Misses      36290    36902     +612     
+ Partials    10755    10722      -33     
Files with missing lines Coverage Δ
...ceGeneration.Core/Generators/NodeStateGenerator.cs 86.96% <100.00%> (+0.03%) ⬆️
tools/Opc.Ua.SourceGeneration/ModelCompilation.cs 69.68% <100.00%> (+0.49%) ⬆️
...ls/Opc.Ua.SourceGeneration/ModelSourceGenerator.cs 100.00% <100.00%> (ø)
...rceGeneration.Core/Schema/ModelDesignExtensions.cs 86.34% <85.71%> (-0.02%) ⬇️
...neration.Core/Generators/FluentBuilderGenerator.cs 85.92% <76.92%> (-0.13%) ⬇️
...ols/Opc.Ua.SourceGeneration/OpcUaStateTypeIndex.cs 71.42% <71.42%> (ø)
...eration.Core/Schema/StandardMethodStateFallback.cs 77.50% <77.50%> (ø)

... and 47 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

marcschier added a commit that referenced this pull request Jul 21, 2026
…e writes (#4063)

## Summary

Combines the compiled companion-model support from #4062 with the
NodeState emission fixes already in this PR. The model source generator
can now consume compiled companion `NodeSet2` models more reliably,
while generated factories consistently target base `NodeState` members
even when a generated subclass shadows them.

## Changes

### Compiled companion NodeSets

- Add `OpcUaStateTypeIndex` and wire it through `ModelSourceGenerator` /
`ModelCompilation` so standard (`ns=0`) node-state and method-state
class names are resolved from referenced `Opc.Ua.Core(.Types)` metadata.
- Add the model-generation-scoped `StandardMethodStateFallback`, which
degrades an unresolved standard typed `*MethodState` reference to
`global::Opc.Ua.MethodState` only when the type is neither available in
the compilation nor declared by the current generation pass.
- Keep declaration names outside the reference-only fallback policy,
propagate fallback state through the logical execution context, reuse
the immutable availability index, and use named incremental-generator
inputs instead of deep `Left` / `Right` chains.
- Escape generated fluent-builder XML documentation where browse names
can contain XML-sensitive characters.

### Safe NodeState factory emission

- Declare one base-typed `global::Opc.Ua.NodeState nodeState = state;`
local per generated factory.
- Use `nodeState` for base-member reads and writes to `NodeId`,
`SymbolicName`, `BrowseName`, `DisplayName`, `Description`, `WriteMask`,
`UserWriteMask`, `AccessRestrictions`, `ReleaseStatus`, `Categories`,
`Specification` and `IsPartOfTypeHierarchy`.
- Base-qualify the optional-child `previousNodeId` read, NodeId
comparison, and existing-child NodeId update.
- `global::`-qualify emitted `NodeId` / `ExpandedNodeId` namespace
prefixes to avoid namespace collisions.

### Supporting updates

- Expose `Opc.Ua.Types` internals to `Opc.Ua.Core.Encoders.Tests`.
- Update generator assertions and add focused fallback regression
coverage.

## Why

Compiled companion models can instantiate standard types whose curated
Core implementation does not expose the conventionally named typed
method-state class. The scoped fallback lets those models generate
without weakening the Stack generator or masking classes emitted by the
current model.

Separately, generated subclasses can shadow base `NodeState` properties
with strongly typed `new` properties. Routing base attributes through a
`NodeState`-typed local guarantees initialization reaches the intended
base member while keeping generated code smaller and clearer than
repeated casts.

## Validation

- Full `Opc.Ua.SourceGeneration.Core.Tests` on net10.0: **3718 passed /
8 skipped / 0 failed**.
- Combined focused generator coverage on net48: **21 passed / 0
failed**.
- Full `Opc.Ua.SourceGeneration.Tests`: **68 passed / 0 failed**.
- Full `Opc.Ua.SourceGeneration.Stack.Tests`: **90 passed / 0 failed**.

## Context

This PR supersedes #4062 and contains both source-generator change sets.
The companion-model fallback remains the scoped interim workaround
tracked by #4060; a future upstream name-resolution step can remove it.
These changes also feed the experimental data-encodings work in #4007.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@marcschier
marcschier deleted the marcschier/sourcegen-companion-models branch July 24, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants