@apollo/federation-internals 2.0.0-preview.8 → 2.0.1

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 (101) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/buildSchema.js +1 -1
  3. package/dist/buildSchema.js.map +1 -1
  4. package/dist/coreSpec.d.ts +13 -6
  5. package/dist/coreSpec.d.ts.map +1 -1
  6. package/dist/coreSpec.js +105 -38
  7. package/dist/coreSpec.js.map +1 -1
  8. package/dist/definitions.d.ts +25 -11
  9. package/dist/definitions.d.ts.map +1 -1
  10. package/dist/definitions.js +115 -63
  11. package/dist/definitions.js.map +1 -1
  12. package/dist/directiveAndTypeSpecification.d.ts +11 -1
  13. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  14. package/dist/directiveAndTypeSpecification.js +77 -20
  15. package/dist/directiveAndTypeSpecification.js.map +1 -1
  16. package/dist/error.d.ts +13 -0
  17. package/dist/error.d.ts.map +1 -1
  18. package/dist/error.js +28 -2
  19. package/dist/error.js.map +1 -1
  20. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  21. package/dist/extractSubgraphsFromSupergraph.js +7 -1
  22. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  23. package/dist/federation.d.ts +19 -6
  24. package/dist/federation.d.ts.map +1 -1
  25. package/dist/federation.js +107 -80
  26. package/dist/federation.js.map +1 -1
  27. package/dist/federationSpec.d.ts +3 -3
  28. package/dist/federationSpec.d.ts.map +1 -1
  29. package/dist/federationSpec.js +34 -26
  30. package/dist/federationSpec.js.map +1 -1
  31. package/dist/inaccessibleSpec.d.ts +11 -5
  32. package/dist/inaccessibleSpec.d.ts.map +1 -1
  33. package/dist/inaccessibleSpec.js +631 -29
  34. package/dist/inaccessibleSpec.js.map +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/introspection.d.ts.map +1 -1
  40. package/dist/introspection.js +8 -3
  41. package/dist/introspection.js.map +1 -1
  42. package/dist/joinSpec.d.ts +6 -2
  43. package/dist/joinSpec.d.ts.map +1 -1
  44. package/dist/joinSpec.js +6 -0
  45. package/dist/joinSpec.js.map +1 -1
  46. package/dist/operations.d.ts +1 -0
  47. package/dist/operations.d.ts.map +1 -1
  48. package/dist/operations.js +16 -1
  49. package/dist/operations.js.map +1 -1
  50. package/dist/{sharing.d.ts → precompute.d.ts} +1 -1
  51. package/dist/precompute.d.ts.map +1 -0
  52. package/dist/{sharing.js → precompute.js} +3 -3
  53. package/dist/precompute.js.map +1 -0
  54. package/dist/schemaUpgrader.d.ts.map +1 -1
  55. package/dist/schemaUpgrader.js +17 -7
  56. package/dist/schemaUpgrader.js.map +1 -1
  57. package/dist/suggestions.d.ts +1 -1
  58. package/dist/suggestions.d.ts.map +1 -1
  59. package/dist/suggestions.js.map +1 -1
  60. package/dist/supergraphs.d.ts.map +1 -1
  61. package/dist/supergraphs.js +2 -0
  62. package/dist/supergraphs.js.map +1 -1
  63. package/dist/tagSpec.d.ts +6 -2
  64. package/dist/tagSpec.d.ts.map +1 -1
  65. package/dist/tagSpec.js +30 -14
  66. package/dist/tagSpec.js.map +1 -1
  67. package/dist/validate.js +13 -7
  68. package/dist/validate.js.map +1 -1
  69. package/dist/values.d.ts +2 -2
  70. package/dist/values.d.ts.map +1 -1
  71. package/dist/values.js +13 -11
  72. package/dist/values.js.map +1 -1
  73. package/package.json +4 -4
  74. package/src/__tests__/coreSpec.test.ts +112 -0
  75. package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
  76. package/src/__tests__/subgraphValidation.test.ts +357 -1
  77. package/src/__tests__/values.test.ts +315 -3
  78. package/src/buildSchema.ts +1 -1
  79. package/src/coreSpec.ts +161 -48
  80. package/src/definitions.ts +223 -90
  81. package/src/directiveAndTypeSpecification.ts +98 -21
  82. package/src/error.ts +80 -2
  83. package/src/extractSubgraphsFromSupergraph.ts +7 -1
  84. package/src/federation.ts +124 -97
  85. package/src/federationSpec.ts +37 -30
  86. package/src/inaccessibleSpec.ts +983 -49
  87. package/src/index.ts +1 -0
  88. package/src/introspection.ts +8 -3
  89. package/src/joinSpec.ts +19 -4
  90. package/src/operations.ts +15 -0
  91. package/src/{sharing.ts → precompute.ts} +3 -6
  92. package/src/schemaUpgrader.ts +29 -13
  93. package/src/suggestions.ts +1 -1
  94. package/src/supergraphs.ts +2 -0
  95. package/src/tagSpec.ts +42 -16
  96. package/src/validate.ts +20 -9
  97. package/src/values.ts +39 -12
  98. package/tsconfig.test.tsbuildinfo +1 -1
  99. package/tsconfig.tsbuildinfo +1 -1
  100. package/dist/sharing.d.ts.map +0 -1
  101. package/dist/sharing.js.map +0 -1
@@ -1,8 +1,12 @@
1
- import { DirectiveLocation, GraphQLError } from "graphql";
1
+ import { ASTNode, DirectiveLocation, GraphQLError } from "graphql";
2
2
  import {
3
3
  ArgumentDefinition,
4
4
  DirectiveDefinition,
5
+ EnumType,
5
6
  InputType,
7
+ isCustomScalarType,
8
+ isEnumType,
9
+ isListType,
6
10
  isNonNullType,
7
11
  isObjectType,
8
12
  isUnionType,
@@ -49,13 +53,16 @@ export function createDirectiveSpecification({
49
53
  name: string,
50
54
  locations: DirectiveLocation[],
51
55
  repeatable?: boolean,
52
- argumentFct?: (schema: Schema) => ArgumentSpecification[],
56
+ argumentFct?: (schema: Schema, nameInSchema?: string) => { args: ArgumentSpecification[], errors: GraphQLError[] },
53
57
  }): DirectiveSpecification {
54
58
  return {
55
59
  name,
56
60
  checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
57
- const args = argumentFct ? argumentFct(schema) : [];
58
61
  const actualName = nameInSchema ?? name;
62
+ const {args, errors} = argumentFct ? argumentFct(schema, actualName) : { args: [], errors: []};
63
+ if (errors.length > 0) {
64
+ return errors;
65
+ }
59
66
  const existing = schema.directive(actualName);
60
67
  if (existing) {
61
68
  return ensureSameDirectiveStructure({name: actualName, locations, repeatable, args}, existing);
@@ -131,7 +138,7 @@ export function createObjectTypeSpecification({
131
138
  errors = errors.concat(ensureSameArguments(
132
139
  { name, args },
133
140
  existingField,
134
- `field ${existingField.coordinate}`,
141
+ `field "${existingField.coordinate}"`,
135
142
  ));
136
143
  }
137
144
  return errors;
@@ -198,6 +205,44 @@ export function createUnionTypeSpecification({
198
205
  }
199
206
  }
200
207
 
208
+ export function createEnumTypeSpecification({
209
+ name,
210
+ values,
211
+ }: {
212
+ name: string,
213
+ values: { name: string, description?: string}[],
214
+ }): TypeSpecification {
215
+ return {
216
+ name,
217
+ checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
218
+ const actualName = nameInSchema ?? name;
219
+ const existing = schema.type(actualName);
220
+ const expectedValueNames = values.map((v) => v.name).sort((n1, n2) => n1.localeCompare(n2));
221
+ if (existing) {
222
+ let errors = ensureSameTypeKind('EnumType', existing);
223
+ if (errors.length > 0) {
224
+ return errors;
225
+ }
226
+ assert(isEnumType(existing), 'Should be an enum type');
227
+ const actualValueNames = existing.values.map(v => v.name).sort((n1, n2) => n1.localeCompare(n2));
228
+ if (!arrayEquals(expectedValueNames, actualValueNames)) {
229
+ errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
230
+ message: `Invalid definition of type ${name}: expected values [${expectedValueNames}] but found [${actualValueNames}].`,
231
+ nodes: existing.sourceAST
232
+ }));
233
+ }
234
+ return errors;
235
+ } else {
236
+ const type = schema.addType(new EnumType(actualName, asBuiltIn));
237
+ for (const {name, description} of values) {
238
+ type.addValue(name).description = description;
239
+ }
240
+ return [];
241
+ }
242
+ },
243
+ }
244
+ }
245
+
201
246
  function ensureSameTypeKind(expected: NamedType['kind'], actual: NamedType): GraphQLError[] {
202
247
  return expected === actual.kind
203
248
  ? []
@@ -216,18 +261,19 @@ function ensureSameDirectiveStructure(
216
261
  },
217
262
  actual: DirectiveDefinition<any>,
218
263
  ): GraphQLError[] {
219
- let errors = ensureSameArguments(expected, actual, `directive ${expected}`);
264
+ const directiveName = `"@${expected.name}"`
265
+ let errors = ensureSameArguments(expected, actual, `directive ${directiveName}`);
220
266
  // It's ok to say you'll never repeat a repeatable directive. It's not ok to repeat one that isn't.
221
267
  if (!expected.repeatable && actual.repeatable) {
222
268
  errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
223
- message: `Invalid definition for directive ${expected}: ${expected} should${expected.repeatable ? "" : " not"} be repeatable`,
269
+ message: `Invalid definition for directive ${directiveName}: ${directiveName} should${expected.repeatable ? "" : " not"} be repeatable`,
224
270
  nodes: actual.sourceAST
225
271
  }));
226
272
  }
227
273
  // Similarly, it's ok to say that you will never use a directive in some locations, but not that you will use it in places not allowed by what is expected.
228
274
  if (!actual.locations.every(loc => expected.locations.includes(loc))) {
229
275
  errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
230
- message: `Invalid efinition for directive ${expected}: ${expected} should have locations ${expected.locations.join(', ')}, but found (non-subset) ${actual.locations.join(', ')}`,
276
+ message: `Invalid definition for directive ${directiveName}: ${directiveName} should have locations ${expected.locations.join(', ')}, but found (non-subset) ${actual.locations.join(', ')}`,
231
277
  nodes: actual.sourceAST
232
278
  }));
233
279
  }
@@ -241,17 +287,24 @@ function ensureSameArguments(
241
287
  },
242
288
  actual: { argument(name: string): ArgumentDefinition<any> | undefined, arguments(): readonly ArgumentDefinition<any>[] },
243
289
  what: string,
290
+ containerSourceAST?: ASTNode,
244
291
  ): GraphQLError[] {
245
292
  const expectedArguments = expected.args ?? [];
246
- const foundArguments = actual.arguments();
247
- if (expectedArguments.length !== foundArguments.length) {
248
- return [ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
249
- message: `Invalid definition for ${what}: should have ${expectedArguments.length} arguments but ${foundArguments.length} found`,
250
- })];
251
- }
252
- let errors: GraphQLError[] = [];
293
+ const errors: GraphQLError[] = [];
253
294
  for (const { name, type, defaultValue } of expectedArguments) {
254
- const actualArgument = actual.argument(name)!;
295
+ const actualArgument = actual.argument(name);
296
+ if (!actualArgument) {
297
+ // Not declaring an optional argument is ok: that means you won't be able to pass a non-default value in your schema, but we allow you that.
298
+ // But missing a required argument it not ok.
299
+ if (isNonNullType(type) && defaultValue === undefined) {
300
+ errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
301
+ message: `Invalid definition for ${what}: missing required argument "${name}"`,
302
+ nodes: containerSourceAST
303
+ }));
304
+ }
305
+ continue;
306
+ }
307
+
255
308
  let actualType = actualArgument.type!;
256
309
  if (isNonNullType(actualType) && !isNonNullType(type)) {
257
310
  // It's ok to redefine an optional argument as mandatory. For instance, if you want to force people on your team to provide a "deprecation reason", you can
@@ -259,14 +312,23 @@ function ensureSameArguments(
259
312
  // is optional if you so wish.
260
313
  actualType = actualType.ofType;
261
314
  }
262
- if (!sameType(type, actualType)) {
263
- errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
264
- message: `Invalid definition of ${what}: ${name} should have type ${type} but found type ${actualArgument.type!}`,
315
+ if (!sameType(type, actualType) && !isValidInputTypeRedefinition(type, actualType)) {
316
+ errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
317
+ message: `Invalid definition for ${what}: argument "${name}" should have type "${type}" but found type "${actualArgument.type!}"`,
318
+ nodes: actualArgument.sourceAST
319
+ }));
320
+ } else if (!isNonNullType(actualArgument.type!) && !valueEquals(defaultValue, actualArgument.defaultValue)) {
321
+ errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
322
+ message: `Invalid definition for ${what}: argument "${name}" should have default value ${valueToString(defaultValue)} but found default value ${valueToString(actualArgument.defaultValue)}`,
265
323
  nodes: actualArgument.sourceAST
266
324
  }));
267
- } else if (!isNonNullType(actualType) && !valueEquals(defaultValue, actualArgument.defaultValue)) {
268
- errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
269
- message: `Invalid definition of ${what}: ${name} should have default value ${valueToString(defaultValue)} but found default value ${valueToString(actualArgument.defaultValue)}`,
325
+ }
326
+ }
327
+ for (const actualArgument of actual.arguments()) {
328
+ // If it's an expect argument, we already validated it. But we still need to reject unkown argument.
329
+ if (!expectedArguments.some((arg) => arg.name === actualArgument.name)) {
330
+ errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
331
+ message: `Invalid definition for ${what}: unknown/unsupported argument "${actualArgument.name}"`,
270
332
  nodes: actualArgument.sourceAST
271
333
  }));
272
334
  }
@@ -274,3 +336,18 @@ function ensureSameArguments(
274
336
  return errors;
275
337
  }
276
338
 
339
+ function isValidInputTypeRedefinition(expectedType: InputType, actualType: InputType): boolean {
340
+ // If the expected type is a custom scalar, then we allow the redefinition to be another type (unless it's a custom scalar, in which
341
+ // case it has to be the same scalar). The rational being that since graphQL does no validation of values passed to a custom scalar,
342
+ // any code that gets some value as input for a custom scalar has to do validation manually, and so there is little harm in allowing
343
+ // a redefinition with another type since any truly invalid value would failed that "manual validation". In practice, this leeway
344
+ // make sense because many scalar will tend to accept only one kind of values (say, strings) and exists only to inform that said string
345
+ // needs to follow a specific format, and in such case, letting user redefine the type as String adds flexibility while doing little harm.
346
+ if (isListType(expectedType)) {
347
+ return isListType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
348
+ }
349
+ if (isNonNullType(expectedType)) {
350
+ return isNonNullType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
351
+ }
352
+ return isCustomScalarType(expectedType) && !isCustomScalarType(actualType);
353
+ }
package/src/error.ts CHANGED
@@ -346,12 +346,62 @@ const LINK_IMPORT_NAME_MISMATCH = makeCodeDefinition(
346
346
 
347
347
  const REFERENCED_INACCESSIBLE = makeCodeDefinition(
348
348
  'REFERENCED_INACCESSIBLE',
349
- 'An element is marked as @inaccessible but is referenced by a non-inaccessible element.'
349
+ 'An element is marked as @inaccessible but is referenced by an element visible in the API schema.'
350
+ );
351
+
352
+ const DEFAULT_VALUE_USES_INACCESSIBLE = makeCodeDefinition(
353
+ 'DEFAULT_VALUE_USES_INACCESSIBLE',
354
+ 'An element is marked as @inaccessible but is used in the default value of an element visible in the API schema.'
355
+ );
356
+
357
+ const QUERY_ROOT_TYPE_INACCESSIBLE = makeCodeDefinition(
358
+ 'QUERY_ROOT_TYPE_INACCESSIBLE',
359
+ 'An element is marked as @inaccessible but is the query root type, which must be visible in the API schema.'
360
+ );
361
+
362
+ const REQUIRED_INACCESSIBLE = makeCodeDefinition(
363
+ 'REQUIRED_INACCESSIBLE',
364
+ 'An element is marked as @inaccessible but is required by an element visible in the API schema.'
365
+ );
366
+
367
+ const IMPLEMENTED_BY_INACCESSIBLE = makeCodeDefinition(
368
+ 'IMPLEMENTED_BY_INACCESSIBLE',
369
+ 'An element is marked as @inaccessible but implements an element visible in the API schema.'
370
+ );
371
+
372
+ const DISALLOWED_INACCESSIBLE = makeCodeDefinition(
373
+ 'DISALLOWED_INACCESSIBLE',
374
+ 'An element is marked as @inaccessible that is not allowed to be @inaccessible.'
375
+ );
376
+
377
+ const ONLY_INACCESSIBLE_CHILDREN = makeCodeDefinition(
378
+ 'ONLY_INACCESSIBLE_CHILDREN',
379
+ 'A type visible in the API schema has only @inaccessible children.'
380
+ );
381
+
382
+ const REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
383
+ 'REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH',
384
+ 'A field of an input object type is mandatory in some subgraphs, but the field is not defined in all the subgraphs that define the input object type.'
350
385
  );
351
386
 
352
387
  const REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
353
388
  'REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH',
354
- 'An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all subgraphs that define the field or directive definition.'
389
+ 'An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all the subgraphs that define the field or directive definition.'
390
+ );
391
+
392
+ const EMPTY_MERGED_INPUT_TYPE = makeCodeDefinition(
393
+ 'EMPTY_MERGED_INPUT_TYPE',
394
+ 'An input object type has no field common to all the subgraphs that define the type. Merging that type would result in an invalid empty input object type.'
395
+ );
396
+
397
+ const ENUM_VALUE_MISMATCH = makeCodeDefinition(
398
+ 'ENUM_VALUE_MISMATCH',
399
+ 'An enum type that is used as both an input and output type has a value that is not defined in all the subgraphs that define the enum type.'
400
+ );
401
+
402
+ const EMPTY_MERGED_ENUM_TYPE = makeCodeDefinition(
403
+ 'EMPTY_MERGED_ENUM_TYPE',
404
+ 'An enum type has no value common to all the subgraphs that define the type. Merging that type would result in an invalid empty enum type.'
355
405
  );
356
406
 
357
407
  const SATISFIABILITY_ERROR = makeCodeDefinition(
@@ -359,6 +409,21 @@ const SATISFIABILITY_ERROR = makeCodeDefinition(
359
409
  'Subgraphs can be merged, but the resulting supergraph API would have queries that cannot be satisfied by those subgraphs.',
360
410
  );
361
411
 
412
+ const OVERRIDE_FROM_SELF_ERROR = makeCodeDefinition(
413
+ 'OVERRIDE_FROM_SELF_ERROR',
414
+ 'Field with `@override` directive has "from" location that references its own subgraph.',
415
+ );
416
+
417
+ const OVERRIDE_SOURCE_HAS_OVERRIDE = makeCodeDefinition(
418
+ 'OVERRIDE_SOURCE_HAS_OVERRIDE',
419
+ 'Field which is overridden to another subgraph is also marked @override.',
420
+ );
421
+
422
+ const OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE = makeCodeDefinition(
423
+ 'OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE',
424
+ 'The @override directive cannot be used on external fields, nor to override fields with either @external, @provides, or @requires.',
425
+ );
426
+
362
427
  export const ERROR_CATEGORIES = {
363
428
  DIRECTIVE_FIELDS_MISSING_EXTERNAL,
364
429
  DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
@@ -416,8 +481,21 @@ export const ERRORS = {
416
481
  INVALID_LINK_DIRECTIVE_USAGE,
417
482
  LINK_IMPORT_NAME_MISMATCH,
418
483
  REFERENCED_INACCESSIBLE,
484
+ DEFAULT_VALUE_USES_INACCESSIBLE,
485
+ QUERY_ROOT_TYPE_INACCESSIBLE,
486
+ REQUIRED_INACCESSIBLE,
487
+ DISALLOWED_INACCESSIBLE,
488
+ IMPLEMENTED_BY_INACCESSIBLE,
489
+ ONLY_INACCESSIBLE_CHILDREN,
419
490
  REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH,
491
+ REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH,
492
+ EMPTY_MERGED_INPUT_TYPE,
493
+ ENUM_VALUE_MISMATCH,
494
+ EMPTY_MERGED_ENUM_TYPE,
420
495
  SATISFIABILITY_ERROR,
496
+ OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE,
497
+ OVERRIDE_FROM_SELF_ERROR,
498
+ OVERRIDE_SOURCE_HAS_OVERRIDE,
421
499
  };
422
500
 
423
501
  const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
@@ -181,6 +181,12 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
181
181
  if (args.external) {
182
182
  subgraphField.applyDirective(subgraph.metadata().externalDirective());
183
183
  }
184
+ if (args.usedOverridden) {
185
+ subgraphField.applyDirective(subgraph.metadata().externalDirective(), {'reason': '[overridden]'});
186
+ }
187
+ if (args.override) {
188
+ subgraphField.applyDirective(subgraph.metadata().overrideDirective(), {'from': args.override});
189
+ }
184
190
  }
185
191
  }
186
192
  }
@@ -262,7 +268,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
262
268
  // truly defined, so we've so far added them everywhere with all their fields, but some fields may have been part
263
269
  // of an extension and be only in a few subgraphs), we remove the field or the subgraph would be invalid.
264
270
  for (const subgraph of subgraphs) {
265
- for (const itf of subgraph.schema.types<InterfaceType>('InterfaceType')) {
271
+ for (const itf of subgraph.schema.interfaceTypes()) {
266
272
  // We only look at objects because interfaces are handled by this own loop in practice.
267
273
  const implementations = itf.possibleRuntimeTypes();
268
274
  for (const field of itf.fields()) {