Skip to content

feat(rust): PropertyGet quotation pattern#4800

Closed
Thorium wants to merge 3 commits into
fable-compiler:mainfrom
Thorium:pr/12-rust-propertyget
Closed

feat(rust): PropertyGet quotation pattern#4800
Thorium wants to merge 3 commits into
fable-compiler:mainfrom
Thorium:pr/12-rust-propertyget

Conversation

@Thorium

@Thorium Thorium commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Makes the PropertyGet quotation pattern work on the Rust target, and unifies the System.Reflection.PropertyInfo carrier so reflection and quotations share one representation.

Stacked on #4787 (feat(rust): reflection) — the diff to review is the last commit only.

The problem

F#'s pattern is PropertyGet(instance: Expr option, propInfo: PropertyInfo, indexerArgs: Expr list) — three slots — and the pattern mapping is shared by every target (Replacements.Util.fs, "PropertyGetPattern" -> isFieldGet). The Rust runtime's isFieldGet returned only two slots and did not normalise the no-instance sentinel, so a PropertyGet pattern could not match at all:

expected a tuple with 3 elements, found one with 2 elements

Fixing the arity alone was not enough: the propInfo slot has to be whatever System.Reflection.PropertyInfo erases to, and that type was erased to the native Reflection_::RecordFieldInfo, which a quotation cannot build (it carries a field-getter closure; a quotation only knows the property name).

The change

PropertyInfo now erases to a small F#-declared carrier, FSharpPropertyInfo = { Name: string }, mirroring how UnionCaseInfo and MethodInfo already erase to FSharpUnionCaseInfo / FSharpMethodInfo in QuotationTypes.fs. PropertyInfo was the odd one out in using a native carrier.

One carrier now serves both reflection (FSharpType.GetRecordFields) and quotations (PropertyGet's propInfo), as in .NET where both yield the same PropertyInfo.

The carrier holds only the name, for two reasons:

  • a quotation knows nothing else about the property, and
  • the generated struct derives Hashable, PartialEq, PartialOrd, which a closure field would break.

The field getter is instead resolved from the reflection registry by name when a value is read, which is also what JS/TS does — getValue(pi, v) => v[pi[0]]. RecordFieldInfo stays as the registry's internal representation.

Testing

./build.sh test rust → 2550 passed, 0 failed (4 new tests):

  • Option.Value and List.Head deconstruct as PropertyGet(Some _, _, [])
  • a static property deconstructs as PropertyGet(None, _, []), proving the "novalue" no-instance sentinel surfaces as None rather than Some sentinelNode
  • pi.Name returns "Value", i.e. the same PropertyInfo member access already used for FSharpType.GetRecordFields results

The 10 existing record-reflection tests still pass, covering the carrier swap.

Also verified: ./build.sh test rust --threaded → 2565 passed; --no_std builds clean.

🤖 Generated with Claude Code

@Thorium Thorium closed this Jul 15, 2026
@Thorium
Thorium deleted the pr/12-rust-propertyget branch July 15, 2026 23:01
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.

1 participant