Skip to content

Accept free-form Any property types in @Record - #58

Merged
tsapeta merged 1 commit into
mainfrom
tsapeta/record-free-form-any-fields
Sep 26, 2026
Merged

tsapeta merged 1 commit into
mainfrom
tsapeta/record-free-form-any-fields

Conversation

@tsapeta

@tsapeta tsapeta commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Why

A @Record property typed [String: Any]? doesn't compile today: every property must be AnyArgument & JavaScriptDecodable & JavaScriptEncodable, and Any can't conform to the codable protocols. There's no conformance-level fix (a second Dictionary conformance or extending Any are both rejected by the compiler), so it has to be handled in the macro. This unblocks the attributes argument on Observe.reportError in expo-observe (ENG-25264).

How

A property whose type mentions Any now converts on the JS-value paths through the JavaScriptValue free-form methods added in expo/expo#50660, not dynamic types:

Property type from(object:) toObject
Any, [Any], [String: Any] (optional or not) decodeAny / decodeAnyArray / decodeAnyDictionary encodeAny / encodeAnyArray / encodeAnyDictionary
other shapes built from Any, arrays, String-keyed dictionaries and optionals ([String: [Any]]?, …) decodeAny(_:as:in:) encodeAny(_:in:)

Any other type holding Any, like [Int: Any] or Box<Any>, is a compile-time error pointing to JavaScriptValue, since those conversions could never produce or accept it.

Such a property only asserts AnyArgument, which the dictionary paths still need, since they keep casting through getDynamicType() like every other property. A bare Any has no dynamic type, so it asserts nothing and the dictionary paths pass it through unchanged.

The generated code calls methods that only exist once expo/expo#50660 lands, so a release with this change needs a core that includes it.

Test Plan

New RecordMacroTests expansion tests cover the dedicated shapes, the generic path, bare Any, Swift.Any, and the diagnostic for shapes that can't convert. The full swift test run passes (286 tests).

A `@Record` property typed `[String: Any]?` (or any type holding `Any`)
failed the conformance assertion, since `Any` can't be
`JavaScriptDecodable & JavaScriptEncodable`. Such a property now converts
on the JS-value paths through the `JavaScriptValue` free-form methods from
`expo-modules-jsi`: `decodeAny`/`decodeAnyArray`/`decodeAnyDictionary` and
the matching `encodeAny…` for `Any`, `[Any]` and `[String: Any]` (optional
or not), and the generic `decodeAny(_:as:in:)`/`encodeAny(_:in:)` for other
shapes built from `Any`, arrays, `String`-keyed dictionaries and optionals.
Any other type holding `Any` (`[Int: Any]`, `Box<Any>`) is a compile-time
error, since it could never convert.

Such a property only asserts `AnyArgument`, and a bare `Any` asserts
nothing and passes through the dictionary paths unchanged. `Swift.Any` is
recognized as `Any`. The optional unwrapping is shared with `@ViewProps`
through `optionalWrappedType`.
@tsapeta
tsapeta force-pushed the tsapeta/record-free-form-any-fields branch from 7441316 to 1042335 Compare September 25, 2026 20:27
@tsapeta
tsapeta marked this pull request as ready for review September 25, 2026 20:29
@tsapeta
tsapeta merged commit da0bfb7 into main Sep 26, 2026
1 check passed
@tsapeta
tsapeta deleted the tsapeta/record-free-form-any-fields branch September 26, 2026 08:47
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