Skip to content

Audit key-membership logic: ITableTypeSymbol.Keys omits the implicit primary key of tables without a keys section #546

Description

@Arthurvdv

Why

Two of the three PC0029 false negatives in #544 had one root cause that has nothing to do with receivers: the table was declared without a keys section, and PC0029 decided key membership from ITableTypeSymbol.Keys. That collection never contains the compiler-synthesized primary key. PC0029 got an inline fix in #544; this issue asks to audit the rest of the rule set and to document the SDK behaviour once.

SDK facts (decompiled Microsoft.Dynamics.Nav.CodeAnalysis, identical in net8.0 and net10.0)

  • TableTypeSymbol.GetPrimaryKey() (Symbols/TableTypeSymbol.cs) returns the first declared key or, when the table declares none, a SynthesizedKeySymbol over the lowest-Id non-synthesized field whose type is a valid key type. Not literally "the first field": a field with an invalid key type is skipped.
  • SynthesizedKeySymbol has IsSynthesized == true, Location == null, DeclaringSyntaxReference == null, no properties, and Fields populated with that one field.
  • ITableTypeSymbol.PrimaryKey returns it. ITableTypeSymbol.Keys is declaration-only (SourceTableTypeSymbol.GetKeys collects SymbolKind.Key members) and is empty for such a table. The same holds for tables loaded from a referenced .app: SerializableSymbolModelConverter.ConvertKeys serializes declared key members only.
  • Consequence: any key-membership or index logic built on Keys is blind to tables without a keys section, in the current app and in every dependency.

What to do

  1. Grep every analyzer and code fix for .Keys, IKeySymbol, KeyListSyntax, KeySyntax and key-related properties (Clustered, Unique, IncludedFields, SumIndexFields, MaintainSiftIndex) and decide per rule whether the synthesized primary key must participate. Read PrimaryKey where it must; the synthesized key is recognisable by IsSynthesized and a null Location when a rule needs to treat it differently (it has no syntax to report on).
  2. Add a fixture whose table has no keys section to every rule that reads keys (.claude/rules/testing.md rule 6 now requires it), including rules that already read PrimaryKey, so the behaviour is pinned rather than assumed.
  3. Consider a small Common helper that yields PrimaryKey followed by the declared Keys without duplicates, if the audit finds more than one consumer.

Background and reviewer rows: .claude/rules/receiver-forms.md (section "Table shape: the implicit primary key"), REVIEW.md house rules.

Upstream note (material for a Microsoft issue)

Microsoft's own cops share the gap: Rule210SuboptimalIndex (Microsoft.Dynamics.Nav.CodeCop.Design) collects table.Keys only, Rule222SIFTIndexShouldNotBeUsedForPrimaryAndUniqueKey bails out when Keys.IsDefaultOrEmpty, and TableFieldsAnalyzer reads Keys as well, so a table without a keys section is invisible to all three. Rule0242PartialRecordsDetectJitLoads reads PrimaryKey.Fields and is correct. This could be reported to Microsoft as a CodeCop gap.

Related: #544 (PC0029 fix), and #545 (receiver-form audit).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions