Repository navigation
feat: AOT-safe property access: [KumikoGridItem] generator, column delegates, guarded reflection (#17) - #137
Conversation
…elegates, providers, guarded reflection (#17) - KumikoUI.Core.Data: KumikoPropertyAccessor, IKumikoAccessorProvider, KumikoAccessorRegistry (type and global providers, cached per type and path) and [KumikoGridItem]. - KumikoUI.SourceGenerators (shipped in the Core package under analyzers/): a typed provider per [KumikoGridItem] type, registered by a module initializer; KUMIKO001/002 for unsupported types. - KumikoColumn.ValueAccessor/ValueSetter and EditorDisplaySelector/EditorValueSelector; DrawnComboBox.DisplaySelector/ValueSelector. Delegates win over every other source. - Reflection fallback behind a feature switch (FeatureSwitchDefinition + FeatureGuard); the package's build targets turn it off for PublishAot and TrimMode=full. Compiled expressions only where dynamic code is supported. - Paths resolve per segment: a null intermediate value reads as null instead of throwing; resolved paths are cached on the column (3.2x faster reads than before, 80% less allocation). - IsAotCompatible on Core, SkiaSharp, Maui and Uno; IL2xxx/IL3xxx are errors (Directory.Build.targets). - Fix: the ComboBox editor applied member paths after building its items. - Tests for the accessors, the switch-off mode and the generator; bench/KumikoUI.Benchmarks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #17. Supersedes #1 (not closed here; see the note at the end).
How the grid reads and writes item properties
For every column, the grid tries these in order:
KumikoColumn.ValueAccessor/ValueSetter, andEditorDisplaySelector/EditorValueSelectorfor ComboBox items (DrawnComboBox.DisplaySelector/ValueSelector). Delegates are used for display, sorting, grouping, filtering, summaries and edits.[KumikoGridItem](KumikoUI.Core.Data). A new source generator,KumikoUI.SourceGenerators, ships inside the KumikoUI.Core package underanalyzers/dotnet/cs. It writes a typedIKumikoAccessorProviderper type and registers it with a module initializer.newandoverridemembers, structs (written through the box), records, nullable and generic property types, and keyword-named properties.[Obsolete(error: true)]members.KUMIKO001for generic types andKUMIKO002for types it cannot reference (private, protected or file-local).KumikoAccessorRegistry.Register(type, provider)orAddProvider(provider), for example to read dictionaries or data rows. A provider registered for a type also covers its subclasses.[FeatureSwitchDefinition]+[FeatureGuard(RequiresUnreferencedCode)], plus anIL4000suppression with its justification). KumikoUI.Core'sbuildTransitivetargets set the switch to false forPublishAot=trueandTrimMode=full, so the trimmer removes the fallback. Partial trimming (the MAUI default) leaves it on. TheKumikoReflectionPropertyAccessMSBuild property overrides the automatic choice. The fallback compiles expression trees only where dynamic code is supported, and usesPropertyInfootherwise.With reflection off, a property that no tier can serve reads as null, edits to it are ignored, and a warning is traced once per type and property.
Paths are resolved one segment at a time. A null value part-way along a path now reads as null instead of throwing an NRE (
Address.Citywith a nullAddress). Resolved paths are cached on the column, keyed by item type and registry version.AOT compatibility
IsAotCompatible, with zero IL2xxx/IL3xxx warnings. I checked Maui on catalyst, android and ios, and Uno on the desktop head.Directory.Build.targetsmakes every IL2xxx/IL3xxx analyzer warning an error in those libraries. It also forces the trim analyzer on, because the Apple workloads turn it off for library builds. I confirmed a plantedGetType().GetPropertycall now fails the MAUI build.[KumikoGridItem]types read, sort, write with conversion and walk nested paths.ValueAccessoron it still works.Benchmarks (
bench/KumikoUI.Benchmarks, 10 000 rows viaGetCellValue, Apple M4 Max, BenchmarkDotNet ShortRun)[KumikoGridItem]generatedValueAccessordelegateGenerated and delegate reads meet the acceptance criterion: both are at least as fast as compiled expressions. The remaining allocation is the boxed
inteach read returns. Under Native AOT, generated accessors avoid the expression interpreter entirely.Other changes
EditorDisplayMemberPath/EditorValueMemberPathafter building its items. So items showedToString()and the current value was never selected. A test now covers it.ValueSetterdelegate's own exceptions propagate.Microsoft.CodeAnalysis.CSharp. The generator is built against Roslyn 4.14 on purpose, the oldest compiler it supports.tests/KumikoUI.SourceGenerators.Tests, and both new projects are in the solution.bench/README.mdexplains the benchmarks, and the CHANGELOG has Added, Changed and Fixed entries.Tests
ValueSetterSetCellValueconversionPR #1
This PR supersedes #1, which swapped in plain reflection with no tests or measurements. Closing #1 is your call; I left it open.
🤖 Generated with Claude Code