Clear nullable warnings in the generated interface - #148
Merged
Merged
Conversation
Both C# templates now open with the System.CodeDom banner around an auto-generated element, matching the two sentences the Python target already emits. Compilers, analyzers and formatters then treat the output as generated code whatever the file is named, so a consuming project with nullable reference types enabled reports none of the nullable warnings the emitted PayloadMarshal helper would otherwise raise. Closes harp-tech#138
PayloadMarshal and the payload converters no longer take an ArraySegment. Generated code unwraps the segment from HarpMessage.GetPayload once in each register accessor, so nothing downstream reads the nullable Array property. Reference-typed payload properties are initialized instead of left null, strings to empty, arrays to their declared length, and other types through a parameterless constructor. DeviceDataWriter.Path returns an empty string for an unset file name. With nullable reference types enabled the core and device interfaces now compile with no warnings. A test-only HarpVersion stands in for the Bonsai.Harp type, which has no parameterless constructor. The metadata model no longer exposes PayloadInterfaceType or GetConverterInterfaceType, which move to the internal template helper.
The compiler test helper now strips the auto-generated header, compiles with nullable reference types enabled, and fails on any nullable warning. Every test that compiles generated output goes through the helper.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roslyn already treats any file whose name ends in
.generated.csas generated code, and the C# target emitsDevice.Generated.csandAsyncDevice.Generated.cs, so no downstream analyzer had ever reported a diagnostic against a generated interface. Emitting the<auto-generated>marker states that explicitly instead of leaving it to a file-naming coincidence, and it makes the correctness of the emitted code this repository's own responsibility.Specifically, nullable analysis had never been run against generated output, and running it for the first time reported 22 warnings for the core interface and 35 for the device interface. These turned out to be real defects, so this PR fixes them and adds regression tests to hold this standard going forward.
What the analysis found
Three causes account for all 57:
ArraySegment<byte>.Arrayis nullable on net8.0, and the emittedPayloadMarshalhelpers and payload converters dereferenced it. 21 warnings in each output, asCS8602andCS8604.CS8618.DeviceDataWriter.Pathreturned the result ofPath.GetDirectoryName, which is null when the file name is unset. Two in the device interface, asCS8603.The uninitialized properties matter because those are operator properties a workflow author fills in from the property grid. For example, the default converter for an array is
ArrayConverter, which expands a value into one editable row per element, so an array initialized to its declared length offers exactly the right number of rows to fill in, while a null expands into nothing. The declared length is not recoverable from the editor, since Bonsai is not a code editor and nothing in a workflow surfaces the register length or relates it to the size of the array.Emitting an initializer also makes the compiler check that the declared interface type can be constructed, which is required by
XmlSerializerso that a workflow containing the operator can be saved.What changed in the generated output
PayloadMarshaland the payload converters take an array with an offset and a count instead of anArraySegment, and generated code unwraps the segment returned byHarpMessage.GetPayloadonce in each register accessor. The nine primitive write overloads take no count, since the value type fixes it. Reference-typed payload properties are initialized, strings to empty, arrays to their declared length, and other types through a parameterless constructor.DeviceDataWriter.Pathreturns an empty string for an unset file name.Timestamped accessors deconstruct the payload and the timestamp in one statement rather than binding the pair and reading
ValueandSecondsseparately, so every converting accessor has the same shape.Breaking changes
Any hand-written converter has to be updated. The register-level and member-level converter stubs take an array with an offset and a count, and a converter that calls a primitive
PayloadMarshal.Writehas to drop one argument. That last one reports asCS1503namingstring, because a four-argument call binds to thestringoverload, so the diagnostic does not point at the cause.RegisterInfo.PayloadInterfaceTypeandPayloadMemberInfo.GetConverterInterfaceTypeare removed from the public surface and now sit on the internal template helper. Neither had a consumer, so no downstream code breaks.No known published device is affected, as no use of a custom converter could be found in downstream repositories.
Why the tests carry their own HarpVersion
Bonsai.Harp.HarpVersionis sealed, has no parameterless constructor, and exposesMajorandMinorwith private setters, so it cannot currently be serialized byXmlSerializer. A workflow containingCreateVersionPayloadcannot be saved today, independently of this branch. The generator now emits= new()for a referenceinterfaceTypecarrying no other initializer, which turns that into a compile error rather than a failure at workflow save.tests/HarpVersion.csis a test-only stand-in so the suite can exercise the path until the upstream type gains a parameterless constructor orIXmlSerializable.Regression tests
CompilerTestHelperstrips the auto-generated header, compiles with nullable reference types enabled, and fails on any nullable warning, so every test that compiles generated output is covered. It also fails when a header is present and cannot be stripped, so a drifting marker cannot make the check pass by suppressing what it looks for.Closes #138