expo-modules-macros 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.github/workflows/linux.yml +132 -0
  2. package/.github/workflows/publish.yml +160 -11
  3. package/.github/workflows/swift-format.yml +76 -0
  4. package/.github/workflows/windows.yml +3 -28
  5. package/AGENTS.md +19 -9
  6. package/README.md +27 -8
  7. package/apple/ExpoModulesMacros +0 -0
  8. package/apple/Package.resolved +3 -3
  9. package/apple/Package.swift +1 -2
  10. package/apple/Sources/ExpoModulesMacros/ClosureArguments.swift +39 -14
  11. package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +117 -25
  12. package/apple/Sources/ExpoModulesMacros/EventMacro.swift +29 -15
  13. package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +55 -45
  14. package/apple/Sources/ExpoModulesMacros/ExpoModulesOptimizedMacro.swift +2 -1
  15. package/apple/Sources/ExpoModulesMacros/FreeFormTypes.swift +2 -61
  16. package/apple/Sources/ExpoModulesMacros/JSConstructor.swift +6 -3
  17. package/apple/Sources/ExpoModulesMacros/JSMacro.swift +180 -44
  18. package/apple/Sources/ExpoModulesMacros/JSNameCollisions.swift +84 -0
  19. package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +165 -43
  20. package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +358 -234
  21. package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +48 -32
  22. package/apple/Sources/ExpoModulesMacros/TypeConformanceAssertion.swift +57 -27
  23. package/apple/Sources/ExpoModulesMacros/UnionMacro.swift +23 -9
  24. package/apple/Sources/ExpoModulesMacros/ViewPropsMacro.swift +10 -5
  25. package/apple/Sources/ExpoModulesScanner/CLI.swift +3 -1
  26. package/apple/Sources/ExpoModulesScanner/Core/DetectionVisitor.swift +11 -7
  27. package/apple/Sources/ExpoModulesScanner/Core/ScanBuildConfiguration.swift +2 -1
  28. package/apple/Sources/ExpoModulesScanner/Core/SourceScan.swift +10 -7
  29. package/apple/Sources/ExpoModulesScanner/Exports/Availability.swift +6 -3
  30. package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +3 -2
  31. package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +15 -5
  32. package/apple/Sources/ExpoModulesScanner/Exports/ScanExports.swift +8 -3
  33. package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +127 -45
  34. package/apple/Sources/ExpoModulesScanner/Exports/TypeNode.swift +2 -1
  35. package/apple/Sources/ExpoModulesScanner/Modules/ScanModules.swift +8 -2
  36. package/build/index.d.ts +4 -3
  37. package/build/index.js +33 -18
  38. package/build/types.d.ts +7 -1
  39. package/package.json +7 -3
@@ -0,0 +1,84 @@
1
+ import SwiftDiagnostics
2
+ import SwiftSyntax
3
+ import SwiftSyntaxMacros
4
+
5
+ /// Reports each `@JS` member whose JavaScript name an earlier `@JS` member already binds on the same
6
+ /// JavaScript object. Pass the members of one object only: a shared object's static members bind onto
7
+ /// the constructor and its instance members onto the prototype, so the two sets are checked separately.
8
+ internal func diagnoseJSNameCollisions(
9
+ functions: [JSFunction],
10
+ properties: [JSProperty],
11
+ in context: some MacroExpansionContext
12
+ ) {
13
+ // Each member binds with its own `setProperty` or `defineProperty` call, so of two members with one
14
+ // name, JavaScript would reach only one. Members are compared in source order, so the error lands on
15
+ // every member after the first, whichever kind each one is.
16
+ let members = functions.map(BoundMember.init(function:)) + properties.map(BoundMember.init(property:))
17
+ var firstMembers: [String: BoundMember] = [:]
18
+
19
+ for member in members.sorted(by: { $0.nameToken.position < $1.nameToken.position }) {
20
+ guard let first = firstMembers[member.jsName] else {
21
+ firstMembers[member.jsName] = member
22
+ continue
23
+ }
24
+ let renaming =
25
+ first.renaming == member.renaming ? first.renaming : "\(first.renaming) or \(member.renaming)"
26
+ context.diagnose(
27
+ Diagnostic(
28
+ node: member.nameToken,
29
+ message: JSNameCollisionMessage(
30
+ "The JavaScript name '\(member.jsName)' is already bound by an earlier \(first.kind). A JavaScript object holds one member per name, so JavaScript could reach only one of them. Give one of them another name with \(renaming)."
31
+ ),
32
+ notes: [
33
+ Note(
34
+ node: Syntax(first.nameToken),
35
+ message: JSNameCollisionNote("'\(member.jsName)' is first bound here")
36
+ )
37
+ ]
38
+ )
39
+ )
40
+ }
41
+ }
42
+
43
+ private struct BoundMember {
44
+ let jsName: String
45
+ /// How the message names the member, such as `@JS function`.
46
+ let kind: String
47
+ /// The spelling that gives the member another JavaScript name.
48
+ let renaming: String
49
+ let nameToken: TokenSyntax
50
+
51
+ init(function: JSFunction) {
52
+ // A `.withAsync` variant takes its JavaScript name from the option, not from `@JS("...")`.
53
+ self.jsName = function.jsName
54
+ self.kind = function.isAsyncVariant ? "'.withAsync' variant" : "@JS function"
55
+ self.renaming = function.isAsyncVariant ? ".withAsync(\"...\")" : "@JS(\"...\")"
56
+ self.nameToken = function.nameToken
57
+ }
58
+
59
+ init(property: JSProperty) {
60
+ self.jsName = property.jsName
61
+ self.kind = "@JS property"
62
+ self.renaming = "@JS(\"...\")"
63
+ self.nameToken = property.nameToken
64
+ }
65
+ }
66
+
67
+ private struct JSNameCollisionMessage: DiagnosticMessage {
68
+ let message: String
69
+ let diagnosticID = MessageID(domain: "ExpoModulesMacros", id: "js-name-collision")
70
+ let severity: DiagnosticSeverity = .error
71
+
72
+ init(_ message: String) {
73
+ self.message = message
74
+ }
75
+ }
76
+
77
+ private struct JSNameCollisionNote: NoteMessage {
78
+ let message: String
79
+ let noteID = MessageID(domain: "ExpoModulesMacros", id: "js-name-collision-first")
80
+
81
+ init(_ message: String) {
82
+ self.message = message
83
+ }
84
+ }
@@ -1,23 +1,20 @@
1
1
  import SwiftSyntax
2
2
 
3
- /**
4
- Reads the first string-literal argument of an attribute, e.g. `@JS("doWork")` -> "doWork".
5
- Returns nil if the attribute has no arguments or the first argument is not a string literal.
6
- */
3
+ /// Reads the first string-literal argument of an attribute, e.g. `@JS("doWork")` -> "doWork".
4
+ /// Returns nil if the attribute has no arguments or the first argument is not a string literal.
7
5
  internal func jsNameArgument(of attribute: AttributeSyntax) -> String? {
8
6
  guard let args = attribute.arguments?.as(LabeledExprListSyntax.self),
9
7
  let first = args.first,
10
8
  let str = first.expression.as(StringLiteralExprSyntax.self),
11
- let segment = str.segments.first?.as(StringSegmentSyntax.self) else {
9
+ let segment = str.segments.first?.as(StringSegmentSyntax.self)
10
+ else {
12
11
  return nil
13
12
  }
14
13
  return segment.content.text
15
14
  }
16
15
 
17
- /**
18
- Reads a labeled boolean-literal argument of an attribute, e.g. `@Event(sync: true)` -> true.
19
- Returns nil if the attribute has no argument with that label or its value isn't a boolean literal.
20
- */
16
+ /// Reads a labeled boolean-literal argument of an attribute, e.g. `@Event(sync: true)` -> true.
17
+ /// Returns nil if the attribute has no argument with that label or its value isn't a boolean literal.
21
18
  internal func boolArgument(of attribute: AttributeSyntax, label: String) -> Bool? {
22
19
  guard let args = attribute.arguments?.as(LabeledExprListSyntax.self) else {
23
20
  return nil
@@ -44,7 +41,8 @@ internal func hasJSOption(_ attribute: AttributeSyntax, named option: String) ->
44
41
  return true
45
42
  }
46
43
  if let array = arg.expression.as(ArrayExprSyntax.self),
47
- array.elements.contains(where: { namesOption($0.expression, option) }) {
44
+ array.elements.contains(where: { namesOption($0.expression, option) })
45
+ {
48
46
  return true
49
47
  }
50
48
  }
@@ -69,6 +67,64 @@ internal func isConcurrentJSMember(_ decl: DeclSyntaxProtocol) -> Bool {
69
67
  return hasJSOption(attribute, named: "concurrent")
70
68
  }
71
69
 
70
+ /// The `.withAsync("name")` option of a `@JS` attribute, e.g. `@JS(.withAsync("loadAsync"))` or
71
+ /// `@JS("load", [.withAsync("loadAsync")])`, or `nil` when the attribute doesn't list it. When the
72
+ /// option is listed more than once, the first one counts and `@JS` reports the others.
73
+ internal func asyncVariantOption(of attribute: AttributeSyntax) -> FunctionCallExprSyntax? {
74
+ return asyncVariantOptions(of: attribute).first
75
+ }
76
+
77
+ /// Every `.withAsync(...)` option of a `@JS` attribute, in source order. Like `hasJSOption`, it scans
78
+ /// the arguments and the elements of an array literal.
79
+ internal func asyncVariantOptions(of attribute: AttributeSyntax) -> [FunctionCallExprSyntax] {
80
+ guard let args = attribute.arguments?.as(LabeledExprListSyntax.self) else {
81
+ return []
82
+ }
83
+ return args.flatMap { arg -> [FunctionCallExprSyntax] in
84
+ if let array = arg.expression.as(ArrayExprSyntax.self) {
85
+ return array.elements.compactMap { asyncOptionCall($0.expression) }
86
+ }
87
+ return asyncOptionCall(arg.expression).map { [$0] } ?? []
88
+ }
89
+ }
90
+
91
+ /// The expression as a `.withAsync(...)` call (or `JSOptions.withAsync(...)`), or `nil` for anything
92
+ /// else.
93
+ private func asyncOptionCall(_ expression: ExprSyntax) -> FunctionCallExprSyntax? {
94
+ guard let call = expression.as(FunctionCallExprSyntax.self),
95
+ let member = call.calledExpression.as(MemberAccessExprSyntax.self),
96
+ member.declName.baseName.text == "withAsync"
97
+ else {
98
+ return nil
99
+ }
100
+ return call
101
+ }
102
+
103
+ /// The JavaScript name a `.withAsync("name")` option gives the promise-returning variant, with the
104
+ /// token of its string literal, where a name collision is reported. `nil` when the argument isn't a
105
+ /// single plain string literal, which `@JS` reports.
106
+ internal func asyncVariantName(of option: FunctionCallExprSyntax) -> (name: String, token: TokenSyntax)? {
107
+ guard option.arguments.count == 1,
108
+ let argument = option.arguments.first,
109
+ argument.label == nil,
110
+ let literal = argument.expression.as(StringLiteralExprSyntax.self),
111
+ literal.segments.count == 1,
112
+ let segment = literal.segments.first?.as(StringSegmentSyntax.self)
113
+ else {
114
+ return nil
115
+ }
116
+ return (segment.content.text, segment.content)
117
+ }
118
+
119
+ /// True if the `@JS`-marked declaration lists `.withAsync("name")`. Such a function is left
120
+ /// unstamped, because its promise-returning variant calls it on the concurrent pool.
121
+ internal func hasAsyncVariant(_ decl: DeclSyntaxProtocol) -> Bool {
122
+ guard let attribute = memberAttributes(of: decl).firstAttribute(named: "JS") else {
123
+ return false
124
+ }
125
+ return asyncVariantOption(of: attribute) != nil
126
+ }
127
+
72
128
  /// True if the type is written as an optional: `T?`, `T!`, or the explicit `Optional<T>`. Used to
73
129
  /// decide argument requiredness (an optional parameter may be omitted) and record-field nullability.
74
130
  internal func isOptionalType(_ type: TypeSyntax) -> Bool {
@@ -129,12 +185,82 @@ internal func optionalWrappedType(_ type: TypeSyntax) -> TypeSyntax? {
129
185
  if let identifier = type.as(IdentifierTypeSyntax.self),
130
186
  identifier.name.text == "Optional",
131
187
  let argument = identifier.genericArgumentClause?.arguments.first,
132
- case .type(let wrapped) = argument.argument {
188
+ case .type(let wrapped) = argument.argument
189
+ {
133
190
  return wrapped
134
191
  }
135
192
  return nil
136
193
  }
137
194
 
195
+ /// The element type of `[T]` or `Array<T>`, or `nil` for any other type.
196
+ internal func arrayElementType(_ type: TypeSyntax) -> TypeSyntax? {
197
+ if let array = type.as(ArrayTypeSyntax.self) {
198
+ return array.element
199
+ }
200
+ guard let arguments = genericArguments(of: type, named: "Array"), arguments.count == 1 else {
201
+ return nil
202
+ }
203
+ return arguments[0]
204
+ }
205
+
206
+ /// The value type of `[String: V]` or `Dictionary<String, V>`, or `nil` for any other type, including a
207
+ /// dictionary with a different key type.
208
+ internal func stringKeyedDictionaryValueType(_ type: TypeSyntax) -> TypeSyntax? {
209
+ if let dictionary = type.as(DictionaryTypeSyntax.self) {
210
+ return isStringType(dictionary.key) ? dictionary.value : nil
211
+ }
212
+ guard let arguments = genericArguments(of: type, named: "Dictionary"), arguments.count == 2 else {
213
+ return nil
214
+ }
215
+ return isStringType(arguments[0]) ? arguments[1] : nil
216
+ }
217
+
218
+ /// The element type of `Set<T>`, or `nil` for any other type.
219
+ internal func setElementType(_ type: TypeSyntax) -> TypeSyntax? {
220
+ guard let arguments = genericArguments(of: type, named: "Set"), arguments.count == 1 else {
221
+ return nil
222
+ }
223
+ return arguments[0]
224
+ }
225
+
226
+ /// The type arguments of a generic type spelled `<name><…>` (or `Swift.<name><…>`), or `nil` when the
227
+ /// type has another name or no type arguments.
228
+ private func genericArguments(of type: TypeSyntax, named name: String) -> [TypeSyntax]? {
229
+ let clause: GenericArgumentClauseSyntax?
230
+ if let identifier = type.as(IdentifierTypeSyntax.self), identifier.name.text == name {
231
+ clause = identifier.genericArgumentClause
232
+ } else if let member = type.as(MemberTypeSyntax.self),
233
+ member.name.text == name,
234
+ member.baseType.as(IdentifierTypeSyntax.self)?.name.text == "Swift"
235
+ {
236
+ clause = member.genericArgumentClause
237
+ } else {
238
+ return nil
239
+ }
240
+ guard let clause else {
241
+ return nil
242
+ }
243
+ var types: [TypeSyntax] = []
244
+ for argument in clause.arguments {
245
+ guard case .type(let type) = argument.argument else {
246
+ return nil
247
+ }
248
+ types.append(type)
249
+ }
250
+ return types
251
+ }
252
+
253
+ /// True when the type is `String` or `Swift.String`.
254
+ private func isStringType(_ type: TypeSyntax) -> Bool {
255
+ if let identifier = type.as(IdentifierTypeSyntax.self) {
256
+ return identifier.name.text == "String" && identifier.genericArgumentClause == nil
257
+ }
258
+ if let member = type.as(MemberTypeSyntax.self), member.name.text == "String" {
259
+ return member.baseType.as(IdentifierTypeSyntax.self)?.name.text == "Swift"
260
+ }
261
+ return false
262
+ }
263
+
138
264
  /// True when the modifiers make the member type-level (`static` or `class`). A shared object routes
139
265
  /// such members to its constructor (JS-static) and instance members to its prototype; the `@JS`
140
266
  /// conformance assertion also uses this to emit its peer in the matching metatype context.
@@ -158,26 +284,23 @@ internal func hasDefaultValue(_ parameter: FunctionParameterSyntax) -> Bool {
158
284
  return parameter.defaultValue != nil
159
285
  }
160
286
 
161
- /**
162
- True if the declaration is a `@Event(sync: true)` property. A sync event dispatches inline on the
163
- JS thread instead of scheduling, so `@ExpoModule`/`@SharedObject` stamp it with `@JavaScriptActor`,
164
- making "must be called on the JS thread" a compile-time guarantee at the call site. Async events
165
- (the default) are deliberately not stamped: their `emit` schedules onto the JS thread itself, so
166
- they stay callable from any thread.
167
- */
287
+ /// True if the declaration is a `@Event(sync: true)` property. A sync event dispatches inline on the
288
+ /// JS thread instead of scheduling, so `@ExpoModule`/`@SharedObject` stamp it with `@JavaScriptActor`,
289
+ /// making "must be called on the JS thread" a compile-time guarantee at the call site. Async events
290
+ /// (the default) are deliberately not stamped: their `emit` schedules onto the JS thread itself, so
291
+ /// they stay callable from any thread.
168
292
  internal func isSyncEventMember(_ decl: DeclSyntaxProtocol) -> Bool {
169
293
  guard let varDecl = decl.as(VariableDeclSyntax.self),
170
- let attribute = varDecl.attributes.firstAttribute(named: "Event") else {
294
+ let attribute = varDecl.attributes.firstAttribute(named: "Event")
295
+ else {
171
296
  return false
172
297
  }
173
298
  return boolArgument(of: attribute, label: "sync") == true
174
299
  }
175
300
 
176
- /**
177
- Reads a labeled array-literal argument of an attribute, e.g. `@ExpoModule(classes: [Foo.self, Bar.self])`,
178
- and returns the type names referenced (e.g. `["Foo", "Bar"]`). Each element must be a
179
- `<TypeName>.self` member-access expression; non-conforming elements are skipped silently.
180
- */
301
+ /// Reads a labeled array-literal argument of an attribute, e.g. `@ExpoModule(classes: [Foo.self, Bar.self])`,
302
+ /// and returns the type names referenced (e.g. `["Foo", "Bar"]`). Each element must be a
303
+ /// `<TypeName>.self` member-access expression; non-conforming elements are skipped silently.
181
304
  internal func classListArgument(of attribute: AttributeSyntax, label: String) -> [String] {
182
305
  guard let args = attribute.arguments?.as(LabeledExprListSyntax.self) else {
183
306
  return []
@@ -189,7 +312,8 @@ internal func classListArgument(of attribute: AttributeSyntax, label: String) ->
189
312
  return array.elements.compactMap { element -> String? in
190
313
  guard let memberAccess = element.expression.as(MemberAccessExprSyntax.self),
191
314
  memberAccess.declName.baseName.text == "self",
192
- let base = memberAccess.base else {
315
+ let base = memberAccess.base
316
+ else {
193
317
  return nil
194
318
  }
195
319
  // A bare `CardView.self` has a `DeclReferenceExprSyntax` base; a qualified
@@ -205,11 +329,9 @@ internal func classListArgument(of attribute: AttributeSyntax, label: String) ->
205
329
  return []
206
330
  }
207
331
 
208
- /**
209
- Returns true if the class's inheritance clause names any of the given identifiers.
210
- Matches by base identifier only, so `Module`, `ExpoModulesCore.Module`, and
211
- `Module<Foo>` all match an entry of "Module".
212
- */
332
+ /// Returns true if the class's inheritance clause names any of the given identifiers.
333
+ /// Matches by base identifier only, so `Module`, `ExpoModulesCore.Module`, and
334
+ /// `Module<Foo>` all match an entry of "Module".
213
335
  internal func inheritsFromAny(_ classDecl: ClassDeclSyntax, names: Set<String>) -> Bool {
214
336
  guard let inherited = classDecl.inheritanceClause?.inheritedTypes else {
215
337
  return false
@@ -222,10 +344,8 @@ internal func inheritsFromAny(_ classDecl: ClassDeclSyntax, names: Set<String>)
222
344
  return false
223
345
  }
224
346
 
225
- /**
226
- Returns the rightmost identifier of a type, e.g. `Foo` for `Foo`, `Foo` for
227
- `Module.Foo`, and nil for composed or generic shapes the macro doesn't need to handle.
228
- */
347
+ /// Returns the rightmost identifier of a type, e.g. `Foo` for `Foo`, `Foo` for
348
+ /// `Module.Foo`, and nil for composed or generic shapes the macro doesn't need to handle.
229
349
  internal func baseIdentifier(of type: TypeSyntax) -> String? {
230
350
  if let identifier = type.as(IdentifierTypeSyntax.self) {
231
351
  return identifier.name.text
@@ -236,10 +356,8 @@ internal func baseIdentifier(of type: TypeSyntax) -> String? {
236
356
  return nil
237
357
  }
238
358
 
239
- /**
240
- True if the declaration carries a `@JS` attribute. Works for functions, properties, and inits;
241
- returns false for any other decl kind.
242
- */
359
+ /// True if the declaration carries a `@JS` attribute. Works for functions, properties, and inits;
360
+ /// returns false for any other decl kind.
243
361
  internal func memberHasJSAttribute(_ decl: DeclSyntaxProtocol) -> Bool {
244
362
  if let funcDecl = decl.as(FunctionDeclSyntax.self) {
245
363
  return funcDecl.attributes.firstAttribute(named: "JS") != nil
@@ -256,6 +374,7 @@ internal func memberHasJSAttribute(_ decl: DeclSyntaxProtocol) -> Bool {
256
374
  /// Decides whether the macro should stamp `@JavaScriptActor` on a `@JS`-marked member.
257
375
  /// The macro defers to the user when they've already chosen an isolation:
258
376
  /// - the member opted out with `@JS(.concurrent)`
377
+ /// - the member lists `@JS(.withAsync("name"))`, whose variant runs it on the concurrent pool
259
378
  /// - the `nonisolated` modifier is present on the member
260
379
  /// - any attribute whose name matches a known global actor (`@MainActor`, `@JavaScriptActor`)
261
380
  /// or follows the `*Actor` naming convention is present on the member or its enclosing type
@@ -265,7 +384,7 @@ internal func shouldStampJavaScriptActor(
265
384
  on member: DeclSyntaxProtocol,
266
385
  enclosedBy enclosing: some DeclGroupSyntax
267
386
  ) -> Bool {
268
- if isConcurrentJSMember(member) {
387
+ if isConcurrentJSMember(member) || hasAsyncVariant(member) {
269
388
  return false
270
389
  }
271
390
 
@@ -312,7 +431,7 @@ internal func memberAttributes(of decl: DeclSyntaxProtocol) -> AttributeListSynt
312
431
  return AttributeListSyntax()
313
432
  }
314
433
 
315
- private func hasGlobalActorShape(_ element: AttributeListSyntax.Element) -> Bool {
434
+ internal func hasGlobalActorShape(_ element: AttributeListSyntax.Element) -> Bool {
316
435
  guard let attribute = element.as(AttributeSyntax.self) else {
317
436
  return false
318
437
  }
@@ -345,7 +464,8 @@ extension AttributeListSyntax {
345
464
  internal func firstAttribute(named name: String) -> AttributeSyntax? {
346
465
  for element in self {
347
466
  if let attr = element.as(AttributeSyntax.self),
348
- attr.attributeName.trimmedDescription == name {
467
+ attr.attributeName.trimmedDescription == name
468
+ {
349
469
  return attr
350
470
  }
351
471
  }
@@ -433,7 +553,8 @@ internal func collectProperties(
433
553
  let swiftName = ident.identifier.text
434
554
  // Prefer the explicit annotation; recover the type from a literal default (`var x = false`)
435
555
  // when there's none. `nil` falls back to inference at the use site.
436
- let valueType = binding.typeAnnotation?.type.trimmedDescription
556
+ let valueType =
557
+ binding.typeAnnotation?.type.trimmedDescription
437
558
  ?? binding.initializer.flatMap { inferredLiteralType(of: $0.value) }
438
559
  return JSProperty(
439
560
  swiftName: swiftName,
@@ -441,7 +562,8 @@ internal func collectProperties(
441
562
  valueType: valueType,
442
563
  // A literal-inferred type never mentions `Any`, so only an explicit annotation can be free-form.
443
564
  freeForm: binding.typeAnnotation.flatMap { freeFormConversion(of: $0.type) },
444
- isSettable: isVar && bindingIsSettable(binding)
565
+ isSettable: isVar && bindingIsSettable(binding),
566
+ nameToken: ident.identifier
445
567
  )
446
568
  }
447
569
  }