carrick 0.3.126 → 0.3.128

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.
@@ -130,6 +130,14 @@ const HTTP_V2_RULES = { http: false, message: true, ops: new Set(['request']), m
130
130
  * the method from the member's own name (`get` registers GET) or from a slot.
131
131
  */
132
132
  const SERVER_RULES = { http: false, message: true, ops: new Set(['receive']), methods: ROUTE_METHODS };
133
+ /**
134
+ * A server's rules as `labelledMembers` reads them (carrick#2674): a `send`
135
+ * op is the reply a handler sends back through its reply parameter, on the
136
+ * connection the request came in on, so it names nothing on the wire. Only
137
+ * the stated-parameter reading takes these; `run` keeps `SERVER_RULES`, so
138
+ * no verdict the scanner reads rows from changes.
139
+ */
140
+ const SERVER_REPLY_RULES = { ...SERVER_RULES, ops: new Set(['receive', 'send']), namelessSends: true };
133
141
  /**
134
142
  * The role table: the role alone picks the checks. A role this build does
135
143
  * not read yet (GraphQL executors) answers `role_unsupported`, and `none`
@@ -263,7 +271,8 @@ export class LibraryClaimsVerifier {
263
271
  * A check on an instance or a scope reads the maker or scope claims of the
264
272
  * same request first, whatever their place in it.
265
273
  */
266
- run(fromDir, checks, budgetMs, scanRoot) {
274
+ run(fromDir, checks, budgetMs, scanRoot, options = {}) {
275
+ const { observe, readReplies = false, typePackages = {} } = options;
267
276
  const deadline = performance.now() + budgetMs;
268
277
  const stamp = (check, outcome) => outcome.verdict === 'verified'
269
278
  ? {
@@ -294,7 +303,11 @@ export class LibraryClaimsVerifier {
294
303
  if (check.role === 'http_client' && hasV2Field(check.claim))
295
304
  v2Exports.add(exportKey(check));
296
305
  }
297
- const rulesFor = (check) => check.role === 'http_client' && v2Exports.has(exportKey(check)) ? HTTP_V2_RULES : ROLE_TABLE[check.role];
306
+ const rulesFor = (check) => check.role === 'http_client' && v2Exports.has(exportKey(check))
307
+ ? HTTP_V2_RULES
308
+ : readReplies && check.role === 'server_framework'
309
+ ? SERVER_REPLY_RULES
310
+ : ROLE_TABLE[check.role];
298
311
  for (const check of checks) {
299
312
  const key = exportKey(check);
300
313
  if (!importIndex.has(key)) {
@@ -448,14 +461,20 @@ export class LibraryClaimsVerifier {
448
461
  if (types.some(type => reader.returnSaysNothing(type))) {
449
462
  return { reason: receiverPath.maker ? 'maker_unresolved' : 'export_untyped' };
450
463
  }
464
+ const claim = check.claim;
465
+ const keyBound = claim.kind === 'op' && claim.name !== undefined && 'bound' in claim.name && claim.name.bound === 'key'
466
+ ? verifiedCollects(check.package).length > 0
467
+ : undefined;
451
468
  return {
452
469
  value: types.map(type => ({
453
470
  type,
454
471
  pkg: check.package,
455
- home: reader.homeOf(check.package, [exported.target, ...typeSymbols(type)]),
472
+ home: withTypePackages(reader.homeOf(check.package, [exported.target, ...typeSymbols(type)]), typePackages[check.package]),
456
473
  makerBindsName,
457
474
  scoped: receiverPath.scope !== undefined,
458
475
  makerHolding,
476
+ ...(keyBound === undefined ? {} : { keyBound }),
477
+ madeBy: receiverPath.maker?.member === null && receiverPath.scope === undefined ? check.export : undefined,
459
478
  })),
460
479
  };
461
480
  };
@@ -479,6 +498,32 @@ export class LibraryClaimsVerifier {
479
498
  return { reason: 'unresolved' };
480
499
  return { value: returns };
481
500
  };
501
+ /**
502
+ * The `collects` scope checks of the request (#2474 R6a) that verify,
503
+ * of one package or of any: judged only when a claim reads them, so a
504
+ * request with none judges exactly what it judged before.
505
+ */
506
+ const verifiedCollects = (pkg) => checks
507
+ .map((other, i) => ({ other, i }))
508
+ .filter(({ other }) => other.claim.kind === 'scope' && other.claim.collects !== undefined && (pkg === undefined || other.package === pkg))
509
+ .filter(({ i }) => judge(i).outcome.verdict === 'verified')
510
+ .map(({ i }) => i);
511
+ /**
512
+ * What the request's verified `collects` scopes return, read at their
513
+ * defaults: the collections an adapter's `runs` slot can take, matched
514
+ * across packages (an HTTP framework's adapter serves another
515
+ * package's router). Undefined when there are none.
516
+ */
517
+ const collections = () => {
518
+ const types = new Set();
519
+ for (const i of verifiedCollects()) {
520
+ const built = builtReceiver(checks[i]);
521
+ const read = holdingReturns([judge(i)], built === undefined ? undefined : atDefaults(checks[i], built));
522
+ if ('value' in read)
523
+ read.value.forEach(type => types.add(type));
524
+ }
525
+ return types.size > 0 ? [...types] : undefined;
526
+ };
482
527
  const judge = (index) => {
483
528
  const cached = judged.get(index);
484
529
  if (cached)
@@ -534,16 +579,28 @@ export class LibraryClaimsVerifier {
534
579
  return { outcome: unchecked(receivers.reason) };
535
580
  const built = builtReceiver(check);
536
581
  const builtAtDefaults = built === undefined ? undefined : atDefaults(check, built);
582
+ // An adapter's `runs` is read against the request's collections only
583
+ // when today's reading refuses it (#2474 R6a): `judgeMake` asks.
584
+ const collectionsOf = check.claim.kind === 'make' && check.claim.runs !== undefined ? collections : undefined;
537
585
  const holding = [];
538
586
  const declarations = [];
587
+ const members = [];
588
+ const bodyMembers = [];
539
589
  for (const receiver of receivers.value) {
540
- const result = reader.judgeMessage(receiver, check.claim, rules, builtAtDefaults);
590
+ const result = reader.judgeMessage(receiver, check.claim, rules, builtAtDefaults, collectionsOf);
541
591
  if (result.outcome.verdict !== 'verified')
542
592
  return result;
543
593
  holding.push(...(result.holding ?? []));
544
594
  declarations.push(...(result.outcome.declarations ?? []));
595
+ members.push(...(result.outcome.members ?? []));
596
+ bodyMembers.push(...(result.outcome.bodyMembers ?? []));
545
597
  }
546
- return { outcome: declarations.length > 0 ? { verdict: 'verified', declarations: sortedDeclarations(declarations) } : VERIFIED, holding };
598
+ const verified = declarations.length > 0 ? { verdict: 'verified', declarations: sortedDeclarations(declarations) } : { ...VERIFIED };
599
+ if (verified.verdict === 'verified' && members.length > 0)
600
+ verified.members = members;
601
+ if (verified.verdict === 'verified' && bodyMembers.length > 0)
602
+ verified.bodyMembers = bodyMembers;
603
+ return { outcome: verified, holding };
547
604
  };
548
605
  const semantics = [];
549
606
  for (let index = 0; index < checks.length; index++) {
@@ -552,7 +609,9 @@ export class LibraryClaimsVerifier {
552
609
  semantics.push(stamp(check, unchecked('budget')));
553
610
  continue;
554
611
  }
555
- semantics.push(stamp(check, judge(index).outcome));
612
+ const outcome = judge(index).outcome;
613
+ observe?.(check, outcome);
614
+ semantics.push(stamp(check, outcome));
556
615
  }
557
616
  return {
558
617
  semantics,
@@ -563,6 +622,42 @@ export class LibraryClaimsVerifier {
563
622
  this.project.removeSourceFile(probe);
564
623
  }
565
624
  }
625
+ /**
626
+ * The members a service's server-framework labels name, read with the
627
+ * verifier itself so only a label that verifies in the service's own
628
+ * program counts (carrick#2674): each verified `send` op that carries a
629
+ * `payload` slot, and each verified `receive` op whose `request` names a
630
+ * `body` path. Other checks are not judged.
631
+ */
632
+ labelledMembers(fromDir, checks, budgetMs, scanRoot, typePackages = {}) {
633
+ const sends = new Map();
634
+ const bodies = new Set();
635
+ const wanted = checks.filter(check => check.role === 'server_framework' &&
636
+ check.claim.kind === 'op' &&
637
+ ((check.claim.op === 'send' && check.claim.payload !== undefined) ||
638
+ (check.claim.op === 'receive' && check.claim.request?.body !== undefined)));
639
+ // A receiver an op is read on can need the maker or scope claims beside it.
640
+ const context = checks.filter(check => check.role === 'server_framework' && (check.claim.kind === 'make' || check.claim.kind === 'scope'));
641
+ if (wanted.length === 0)
642
+ return { sends, bodies };
643
+ const outcomes = {};
644
+ const observe = (check, outcome) => {
645
+ if (check.claim.kind !== 'op')
646
+ return;
647
+ const read = outcome.verdict === 'verified' ? 'verified' : `${outcome.verdict} ${outcome.reason}`;
648
+ outcomes[read] = (outcomes[read] ?? 0) + 1;
649
+ if (outcome.verdict !== 'verified')
650
+ return;
651
+ if (check.claim.op === 'send' && check.claim.payload !== undefined) {
652
+ for (const member of outcome.members ?? [])
653
+ sends.set(declarationKey(member), check.claim.payload);
654
+ }
655
+ for (const member of outcome.bodyMembers ?? [])
656
+ bodies.add(declarationKey(member));
657
+ };
658
+ this.run(fromDir, [...context, ...wanted], budgetMs, scanRoot, { observe, readReplies: true, typePackages });
659
+ return { sends, bodies, outcomes };
660
+ }
566
661
  /**
567
662
  * Each specifier's declared surface, read with the verifier's predicates
568
663
  * (carrick#1660): every value export (or only those `only` names for the
@@ -667,7 +762,8 @@ function resolvedBuild(checker, statement) {
667
762
  * array of `[package, exports]` for every surface that listed at least one
668
763
  * export, sorted by `package` (code unit order). A specifier that lists
669
764
  * nothing (unresolved, local, no value exports) is left out, so asking for a
670
- * subpath a version does not have changes nothing. Type text already names
765
+ * subpath a version does not have changes nothing. Handler members are not
766
+ * hashed. Type text already names
671
767
  * the request's directory as `<root>`.
672
768
  */
673
769
  export function fullSurfaceSha256(surfaces) {
@@ -675,7 +771,10 @@ export function fullSurfaceSha256(surfaces) {
675
771
  .filter(surface => surface.reason === undefined && surface.exports.length > 0)
676
772
  .map(surface => [surface.package, surface.exports])
677
773
  .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
678
- return crypto.createHash('sha256').update(JSON.stringify(listed)).digest('hex');
774
+ // A member's `handlers` (carrick#2674) is out of the hash at every depth, so
775
+ // a package's key does not change when the lister learns to list them.
776
+ const text = JSON.stringify(listed, (key, value) => (key === 'handlers' ? undefined : value));
777
+ return crypto.createHash('sha256').update(text).digest('hex');
679
778
  }
680
779
  function exportKey(check) {
681
780
  return JSON.stringify([check.package, check.export]);
@@ -732,17 +831,31 @@ function placementInvalid(claim) {
732
831
  return of === undefined || (of.maker === undefined && of.scope === undefined);
733
832
  }
734
833
  /**
735
- * Where a handler's request holds its path and method (#2474 R0) is read on
736
- * a receive op with a handler, and names `path` or `url`; every member path
737
- * it gives is non-empty.
834
+ * Where a handler's request holds its path, method and body (#2474 R0,
835
+ * carrick#2674) is read on a receive op with a handler, and names `path`,
836
+ * `url` or `body`; every member path it gives is non-empty.
738
837
  */
739
838
  function requestAtValid(claim) {
740
839
  const request = claim.request;
741
840
  if (claim.op !== 'receive' || claim.handler === undefined)
742
841
  return false;
743
- if (request.path === undefined && request.url === undefined)
842
+ if (request.path === undefined && request.url === undefined && request.body === undefined)
744
843
  return false;
745
- return [request.path, request.url, request.method].every(members => members === undefined || members.length > 0);
844
+ return [request.path, request.url, request.method, request.body].every(members => members === undefined || members.length > 0);
845
+ }
846
+ /** A receiver's home packages, with the type-only packages the store lists for its package. */
847
+ function withTypePackages(home, typePackages) {
848
+ return typePackages && typePackages.length > 0 ? new Set([...home, ...typePackages]) : home;
849
+ }
850
+ /**
851
+ * A declaration's key within one process: its file as the compiler names it
852
+ * and its start. Two programs of one project name a declaration alike, so a
853
+ * label read in the verifier's probe program joins a member read in the
854
+ * inferrer's (carrick#2674).
855
+ */
856
+ export function declarationKey(declaration) {
857
+ const file = declaration.getSourceFile();
858
+ return `${file.fileName}:${declaration.getStart(file)}`;
746
859
  }
747
860
  /**
748
861
  * A message check names a receiver its claim can be read on (contract
@@ -1501,10 +1614,10 @@ class DeclarationReader {
1501
1614
  * overloads it holds on. `built` is the probe's no-argument build of the
1502
1615
  * receiver a maker or scope claim makes (see `returnOf`).
1503
1616
  */
1504
- judgeMessage(receiver, claim, rules, built) {
1617
+ judgeMessage(receiver, claim, rules, built, collections) {
1505
1618
  switch (claim.kind) {
1506
1619
  case 'make':
1507
- return this.judgeMake(receiver, claim, built);
1620
+ return this.judgeMake(receiver, claim, built, collections);
1508
1621
  case 'scope':
1509
1622
  return this.judgeScope(receiver, claim, built);
1510
1623
  case 'op':
@@ -1522,8 +1635,15 @@ class DeclarationReader {
1522
1635
  * defaults (`returnOf`). Those overloads are the ones an instance is read
1523
1636
  * through. A definition (`task({ id, run })`) is a maker with a `name` and a
1524
1637
  * `handler` slot; a queue (`new Queue("emails")`) one with a positional name.
1525
- */
1526
- judgeMake(receiver, claim, built) {
1638
+ *
1639
+ * An adapter (#2474 R6a, contract amendment 3 on carrick#2434) is a maker
1640
+ * whose `runs` slot takes a collection rather than a function. Today's
1641
+ * reading runs first, unchanged; only an overload it refuses on `runs` is
1642
+ * read again, when the request holds verified `collects` scopes
1643
+ * (`collections`), with `runs` taking what one of them returns
1644
+ * (`runsCollectionOutcome`). With no such scope this arm never runs.
1645
+ */
1646
+ judgeMake(receiver, claim, built, collections) {
1527
1647
  const layout = this.layout({
1528
1648
  base: claim.base,
1529
1649
  prefix: claim.prefix,
@@ -1541,6 +1661,12 @@ class DeclarationReader {
1541
1661
  let best;
1542
1662
  for (const signature of callee.signatures) {
1543
1663
  let result = this.messageSignatureOutcome(signature, layout.value, labels);
1664
+ if (result.outcome.verdict !== 'verified' && result.outcome.reason.startsWith('runs_') && collections !== undefined) {
1665
+ const collected = collections();
1666
+ if (collected !== undefined) {
1667
+ result = this.messageSignatureOutcome(signature, layout.value, labels, declared => this.runsCollectionOutcome(signature, declared, collected));
1668
+ }
1669
+ }
1544
1670
  if (result.rank === Infinity && this.returnSaysNothing(this.returnOf(signature, built))) {
1545
1671
  result = { rank: 6, outcome: unchecked('maker_unresolved') };
1546
1672
  }
@@ -1569,12 +1695,23 @@ class DeclarationReader {
1569
1695
  * overloads: a `>scope:` receiver read through it is not resolved.
1570
1696
  * `runs` without `yields` (or the reverse), or `mounts` beside either, is
1571
1697
  * `claim_invalid`.
1698
+ *
1699
+ * Procedure routers (#2474 R6a, contract amendment 3 on carrick#2434): a
1700
+ * scope carries exactly one of `name` and `collects`, and `collects` never
1701
+ * beside `runs`, `yields` or `mounts` (`claim_invalid`). With `collects`
1702
+ * the member takes an object of procedures there (`collectsOutcome`), and
1703
+ * its return, the collection, is read as a plain scope's is.
1572
1704
  */
1573
1705
  judgeScope(receiver, claim, built) {
1574
1706
  if ((claim.runs === undefined) !== (claim.yields === undefined))
1575
1707
  return { outcome: unchecked('claim_invalid') };
1576
1708
  if (claim.mounts !== undefined && claim.runs !== undefined)
1577
1709
  return { outcome: unchecked('claim_invalid') };
1710
+ if ((claim.name === undefined) === (claim.collects === undefined))
1711
+ return { outcome: unchecked('claim_invalid') };
1712
+ if (claim.collects !== undefined && (claim.runs !== undefined || claim.mounts !== undefined)) {
1713
+ return { outcome: unchecked('claim_invalid') };
1714
+ }
1578
1715
  const binds = claim.runs !== undefined || claim.mounts !== undefined;
1579
1716
  const layout = this.layout({ name: claim.name, runs: claim.runs, mounts: claim.mounts });
1580
1717
  if ('failure' in layout)
@@ -1597,6 +1734,11 @@ class DeclarationReader {
1597
1734
  if (mounted)
1598
1735
  result = { rank: 5, outcome: mounted };
1599
1736
  }
1737
+ if (result.rank === Infinity && claim.collects !== undefined) {
1738
+ const collected = this.collectsOutcome(signature, claim.collects);
1739
+ if (collected)
1740
+ result = { rank: 5, outcome: collected };
1741
+ }
1600
1742
  if (result.rank === Infinity && !binds && this.returnSaysNothing(this.returnOf(signature, built))) {
1601
1743
  result = { rank: 6, outcome: unchecked('scope_unresolved') };
1602
1744
  }
@@ -1645,6 +1787,63 @@ class DeclarationReader {
1645
1787
  return erased ? undefined : failed('mounts_not_instance');
1646
1788
  return this.checker.isTypeAssignableTo(receiver.type, slot) ? undefined : failed('mounts_not_instance');
1647
1789
  }
1790
+ /**
1791
+ * `collects: { arg, key?, join }` on a scope (#2474 R6a): the parameter
1792
+ * there is an object type that is not a function, whose keys name the
1793
+ * procedures. A parameter typed `any`, `unknown`, `{}` or by an
1794
+ * unconstrained type parameter says nothing (`collects_untyped`).
1795
+ * Undefined when it holds.
1796
+ */
1797
+ collectsOutcome(signature, collects) {
1798
+ let declared = this.keyParameterAt(signature, collects.arg);
1799
+ if (declared !== undefined && collects.key !== undefined) {
1800
+ const property = this.declaredProperty(declared, collects.key);
1801
+ declared = property ? this.throughConstraint(this.checker.getTypeOfSymbol(property)) : undefined;
1802
+ }
1803
+ if (declared === undefined)
1804
+ return failed('param_missing');
1805
+ if (declared === VARIADIC)
1806
+ return unchecked('collects_untyped');
1807
+ const parts = this.parts(declared);
1808
+ if (parts.length === 0)
1809
+ return failed('collects_not_object');
1810
+ if (parts.some(part => this.isOpenTop(part) || this.isUnconstrained(part) || this.isEmptyObject(part))) {
1811
+ return unchecked('collects_untyped');
1812
+ }
1813
+ if (parts.some(part => !this.isObjectLike(part) || part.getCallSignatures().length > 0)) {
1814
+ return failed('collects_not_object');
1815
+ }
1816
+ return undefined;
1817
+ }
1818
+ /**
1819
+ * An adapter's `runs` slot (#2474 R6a) takes one of `collections`, the
1820
+ * types the request's verified `collects` scopes return, read as a mounted
1821
+ * instance is (`fitsErased`, then assignability). A slot typed `any`,
1822
+ * `unknown`, `{}` or by an unconstrained type parameter says nothing
1823
+ * (`runs_untyped`). Undefined when it holds.
1824
+ */
1825
+ runsCollectionOutcome(signature, declared, collections) {
1826
+ if (declared === undefined)
1827
+ return failed('param_missing');
1828
+ const slot = this.throughConstraint(declared);
1829
+ if (slot === VARIADIC)
1830
+ return unchecked('runs_untyped');
1831
+ const parts = this.parts(slot);
1832
+ if (parts.length === 0)
1833
+ return failed('runs_not_collection');
1834
+ if (parts.some(part => this.isOpenTop(part) || this.isUnconstrained(part) || this.isEmptyObject(part))) {
1835
+ return unchecked('runs_untyped');
1836
+ }
1837
+ const own = signature.typeParameters ?? [];
1838
+ for (const collection of collections) {
1839
+ const erased = this.fitsErased(collection, slot, own);
1840
+ if (erased === true)
1841
+ return undefined;
1842
+ if (erased === undefined && this.checker.isTypeAssignableTo(collection, slot))
1843
+ return undefined;
1844
+ }
1845
+ return failed('runs_not_collection');
1846
+ }
1648
1847
  /**
1649
1848
  * `receiver` fits `slot` with the signature's own type parameters
1650
1849
  * (`own`) erased to their constraints (carrick#2652), or undefined when
@@ -1735,6 +1934,15 @@ class DeclarationReader {
1735
1934
  return true;
1736
1935
  if (type.isUnionOrIntersection())
1737
1936
  return type.types.some(part => this.mentions(part, parameter, depth + 1));
1937
+ // A deferred conditional (`Server extends X ? A : B`) or an alias
1938
+ // instance names its parameter in its check type or its alias arguments
1939
+ // (carrick#2663).
1940
+ if (type.aliasTypeArguments?.some(argument => this.mentions(argument, parameter, depth + 1)))
1941
+ return true;
1942
+ if (type.flags & ts.TypeFlags.Conditional) {
1943
+ const conditional = type;
1944
+ return [conditional.checkType, conditional.extendsType].some(part => this.mentions(part, parameter, depth + 1));
1945
+ }
1738
1946
  const reference = this.referenceOf(type);
1739
1947
  if (reference !== undefined) {
1740
1948
  return this.checker.getTypeArguments(reference).some(argument => this.mentions(argument, parameter, depth + 1));
@@ -1766,7 +1974,7 @@ class DeclarationReader {
1766
1974
  // what it runs, and one that hands its handler a receiver (a server's
1767
1975
  // connection event) names nothing on the wire itself, so neither needs a
1768
1976
  // name of its own.
1769
- const nameless = claim.runs !== undefined || claim.yields !== undefined;
1977
+ const nameless = claim.runs !== undefined || claim.yields !== undefined || (claim.op === 'send' && rules.namelessSends === true);
1770
1978
  if ((name === undefined && !nameless) || (claim.op === 'send' && claim.payload === undefined)) {
1771
1979
  return unchecked('claim_invalid');
1772
1980
  }
@@ -1775,7 +1983,8 @@ class DeclarationReader {
1775
1983
  nameSlot = undefined;
1776
1984
  }
1777
1985
  else if ('bound' in name) {
1778
- if (name.bound === 'maker' ? !receiver.makerBindsName : !receiver.scoped)
1986
+ const bound = name.bound === 'maker' ? receiver.makerBindsName : name.bound === 'scope' ? receiver.scoped : receiver.keyBound === true;
1987
+ if (!bound)
1779
1988
  return failed('name_unbound');
1780
1989
  }
1781
1990
  else {
@@ -1802,6 +2011,7 @@ class DeclarationReader {
1802
2011
  const labels = claim.key_labels ?? {};
1803
2012
  let best;
1804
2013
  let held = false;
2014
+ const bodyMembers = [];
1805
2015
  for (const signature of callee.signatures) {
1806
2016
  let result = this.messageSignatureOutcome(signature, layout.value, labels);
1807
2017
  if (result.rank === Infinity && claim.yields !== undefined) {
@@ -1810,7 +2020,7 @@ class DeclarationReader {
1810
2020
  result = { rank: 5, outcome: given };
1811
2021
  }
1812
2022
  if (result.rank === Infinity && claim.request !== undefined) {
1813
- const read = this.requestOutcome(signature, claim.handler, claim.request);
2023
+ const read = this.requestOutcome(signature, claim.handler, claim.request, bodyMembers);
1814
2024
  if (read)
1815
2025
  result = { rank: 5, outcome: read };
1816
2026
  }
@@ -1825,7 +2035,15 @@ class DeclarationReader {
1825
2035
  if (boundRuns)
1826
2036
  return this.boundRunsOutcome(receiver.makerHolding, boundRuns);
1827
2037
  const declarations = this.declarationsOf(callee);
1828
- return declarations.length > 0 ? { verdict: 'verified', declarations } : VERIFIED;
2038
+ const members = callee.named
2039
+ ? (callee.named.property.declarations ?? [])
2040
+ : callee.signatures.flatMap(signature => signature.getDeclaration() ?? []);
2041
+ return {
2042
+ verdict: 'verified',
2043
+ ...(declarations.length > 0 ? { declarations } : {}),
2044
+ ...(members.length > 0 ? { members } : {}),
2045
+ ...(bodyMembers.length > 0 ? { bodyMembers } : {}),
2046
+ };
1829
2047
  }
1830
2048
  /**
1831
2049
  * Where a verified op or scope's member is declared, as position rows spell
@@ -1872,6 +2090,18 @@ class DeclarationReader {
1872
2090
  */
1873
2091
  methodOutcome(claim, rules) {
1874
2092
  const words = rules.methods;
2093
+ // A procedure named by its key (#2474 R6a): a receive op with a handler
2094
+ // whose label states its method, a route word. The member names the
2095
+ // procedure kind (`query`), not a verb, so it need not equal the method.
2096
+ if (claim.name !== undefined && 'bound' in claim.name && claim.name.bound === 'key') {
2097
+ if (!words || claim.op !== 'receive' || claim.handler === undefined || claim.method === undefined) {
2098
+ return unchecked('claim_invalid');
2099
+ }
2100
+ if (claim.method_key !== undefined || claim.method_default !== undefined || claim.options !== undefined) {
2101
+ return unchecked('claim_invalid');
2102
+ }
2103
+ return words.has(claim.method) ? undefined : failed('method_not_word');
2104
+ }
1875
2105
  if (!words) {
1876
2106
  const any = claim.method !== undefined ||
1877
2107
  claim.method_key !== undefined ||
@@ -1927,11 +2157,17 @@ class DeclarationReader {
1927
2157
  return failure.verdict === 'failed' ? failed(reason) : unchecked(reason);
1928
2158
  }
1929
2159
  /**
1930
- * `yields: { param, export, side }` on a receive op: some signature of the
1931
- * handler this overload takes declares a parameter at `param` whose type
1932
- * is the package's own export named `export` (a class, or the interface
1933
- * an instance has), declared in the receiver's home packages. `side` is
1934
- * `client` or `server` (`yields_side_invalid`). Undefined when it holds.
2160
+ * `yields: { param, export, side }` on a receive op or a scope: some
2161
+ * signature of the function this overload takes declares a parameter at
2162
+ * `param` whose type is either
2163
+ * - the package's own export named `export` (a class, or the interface an
2164
+ * instance has), declared in the receiver's home packages; or
2165
+ * - when `export` is the maker whose own call made the instance the claim
2166
+ * is read on (`madeBy`, carrick#2663): that instance, compared by
2167
+ * declaration (`handsInstance`), since a factory's result has no export
2168
+ * name of its own.
2169
+ * `side` is `client` or `server` (`yields_side_invalid`). Undefined when it
2170
+ * holds.
1935
2171
  */
1936
2172
  yieldsOutcome(signature, handler, yields, receiver) {
1937
2173
  if (yields.side !== 'client' && yields.side !== 'server')
@@ -1955,18 +2191,53 @@ class DeclarationReader {
1955
2191
  (symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), receiver.home)));
1956
2192
  if (named)
1957
2193
  return undefined;
2194
+ if (yields.export === receiver.madeBy && this.handsInstance(given, receiver, signature.typeParameters ?? [])) {
2195
+ return undefined;
2196
+ }
1958
2197
  }
1959
2198
  }
1960
2199
  return failed('yields_not_export');
1961
2200
  }
1962
2201
  /**
1963
- * `request: { param, path?, url?, method? }` on a receive op (#2474 R0):
1964
- * some signature of the handler this overload takes declares a parameter
1965
- * at `param` on which every member path the claim gives resolves, step by
1966
- * step, through properties the library declares (`declaredProperty`).
1967
- * Undefined when it holds.
2202
+ * `given` (each part, nullish dropped) is the instance `receiver` holds:
2203
+ * some part of its intersection is a class or interface the receiver's
2204
+ * home packages declare, and the same declaration (the same symbol, never
2205
+ * the same name) as a part of the instance's own intersection. A generic
2206
+ * instance is built at its defaults while `given` threads the overload's
2207
+ * own type parameters (`own`) through its arguments, so the arguments are
2208
+ * read as `fitsErased` reads a mounted instance (carrick#2652): a
2209
+ * definite mismatch refuses, an argument it cannot read leaves the
2210
+ * declaration to decide.
2211
+ */
2212
+ handsInstance(given, receiver, own) {
2213
+ const declared = (type) => {
2214
+ const symbol = type.getSymbol();
2215
+ if (!symbol || !(symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)))
2216
+ return undefined;
2217
+ const home = (symbol.declarations ?? []).some(declaration => this.isOwnFile(declaration.getSourceFile(), receiver.home));
2218
+ return home ? symbol : undefined;
2219
+ };
2220
+ const intersected = (type) => (type.isIntersection() ? type.types : [type]);
2221
+ const instances = this.parts(receiver.type)
2222
+ .flatMap(intersected)
2223
+ .filter(part => declared(part) !== undefined);
2224
+ const parts = this.parts(given);
2225
+ return (parts.length > 0 &&
2226
+ parts.every(part => intersected(part).some(handed => {
2227
+ const symbol = declared(handed);
2228
+ return (symbol !== undefined &&
2229
+ instances.some(instance => declared(instance) === symbol && this.fitsErased(instance, handed, own) !== false));
2230
+ })));
2231
+ }
2232
+ /**
2233
+ * `request: { param, path?, url?, method?, body? }` on a receive op (#2474
2234
+ * R0, carrick#2674): some signature of the handler this overload takes
2235
+ * declares a parameter at `param` on which every member path the claim
2236
+ * gives resolves, step by step, through properties the library declares
2237
+ * (`declaredProperty`). Undefined when it holds; the declarations of the
2238
+ * member the `body` path ends on are then added to `bodyMembers`.
1968
2239
  */
1969
- requestOutcome(signature, handler, request) {
2240
+ requestOutcome(signature, handler, request, bodyMembers) {
1970
2241
  let declared = this.keyParameterAt(signature, handler.arg);
1971
2242
  if (declared !== undefined && handler.key !== undefined) {
1972
2243
  const property = this.declaredProperty(declared, handler.key);
@@ -1977,25 +2248,33 @@ class DeclarationReader {
1977
2248
  const slot = this.throughConditional(this.throughConstraint(declared));
1978
2249
  if (slot === VARIADIC)
1979
2250
  return unchecked('handler_untyped');
1980
- const paths = [request.path, request.url, request.method].filter((p) => p !== undefined);
2251
+ const paths = [request.path, request.url, request.method, request.body].filter((p) => p !== undefined);
1981
2252
  let best = failed('param_missing');
1982
2253
  for (const part of this.parts(slot)) {
1983
2254
  for (const handed of part.getCallSignatures()) {
1984
2255
  const given = this.parameterAt(handed, request.param);
1985
2256
  if (given === undefined || given === VARIADIC)
1986
2257
  continue;
2258
+ const ends = new Map();
1987
2259
  const resolves = paths.every(members => {
1988
2260
  let at = given;
2261
+ let last;
1989
2262
  for (const member of members) {
1990
2263
  const property = this.declaredProperty(at, member);
1991
2264
  if (!property)
1992
2265
  return false;
2266
+ last = property;
1993
2267
  at = this.checker.getTypeOfSymbol(property);
1994
2268
  }
2269
+ if (last)
2270
+ ends.set(members, last);
1995
2271
  return true;
1996
2272
  });
1997
- if (resolves)
2273
+ if (resolves) {
2274
+ const body = request.body ? ends.get(request.body) : undefined;
2275
+ bodyMembers.push(...(body?.declarations ?? []));
1998
2276
  return undefined;
2277
+ }
1999
2278
  best = failed('request_member_missing');
2000
2279
  }
2001
2280
  }
@@ -2060,8 +2339,12 @@ class DeclarationReader {
2060
2339
  * prefix key accepting a string and a handler key a declared function.
2061
2340
  * 4. Every string slot of the call besides the name is accounted for
2062
2341
  * (design D2, `nameSiblings`).
2342
+ *
2343
+ * `runsAs` replaces the `runs` predicate (an adapter's collection, #2474
2344
+ * R6a); absent, a `runs` slot is a declared function.
2063
2345
  */
2064
- messageSignatureOutcome(signature, layout, labels) {
2346
+ messageSignatureOutcome(signature, layout, labels, runsAs) {
2347
+ const runsFailure = runsAs ?? ((declared) => this.runsFailure(declared));
2065
2348
  for (const [arg, part] of layout.positional) {
2066
2349
  if (part === 'method') {
2067
2350
  const declared = this.keyParameterAt(signature, arg);
@@ -2098,7 +2381,7 @@ class DeclarationReader {
2098
2381
  if (part !== 'handler' && part !== 'ack' && part !== 'runs')
2099
2382
  continue;
2100
2383
  const declared = this.keyParameterAt(signature, arg);
2101
- const failure = part === 'runs' ? this.runsFailure(declared) : this.handlerFailure(declared);
2384
+ const failure = part === 'runs' ? runsFailure(declared) : this.handlerFailure(declared);
2102
2385
  if (failure)
2103
2386
  return { rank: 3, outcome: failure };
2104
2387
  }
@@ -2107,7 +2390,7 @@ class DeclarationReader {
2107
2390
  const slot = this.keyParameterAt(signature, arg);
2108
2391
  if (slot === undefined)
2109
2392
  return { rank: 1, outcome: failed('param_missing') };
2110
- const view = this.viewFor(slot, keys);
2393
+ const view = this.viewFor(slot, keys, runsFailure);
2111
2394
  if ('rank' in view)
2112
2395
  return view;
2113
2396
  views.set(arg, view.view);
@@ -2122,7 +2405,7 @@ class DeclarationReader {
2122
2405
  * union member that is an object type and not a function type, declaring
2123
2406
  * every claimed key with the type its part needs.
2124
2407
  */
2125
- viewFor(slot, keys) {
2408
+ viewFor(slot, keys, runsFailure) {
2126
2409
  if (slot === VARIADIC || !this.isObjectType(slot)) {
2127
2410
  return { rank: 1, outcome: this.slotFailure(slot, 'param_missing') };
2128
2411
  }
@@ -2152,7 +2435,7 @@ class DeclarationReader {
2152
2435
  break;
2153
2436
  }
2154
2437
  if (part === 'runs') {
2155
- const bad = this.runsFailure(type);
2438
+ const bad = runsFailure(type);
2156
2439
  if (bad) {
2157
2440
  failure = { rank: 3, outcome: bad };
2158
2441
  break;
@@ -2719,8 +3002,20 @@ class DeclarationReader {
2719
3002
  dropped += 1;
2720
3003
  return false;
2721
3004
  };
2722
- // Three passes: every export and receiver, then every member name, then signatures.
3005
+ // Handler-parameter members (carrick#2674) have a cap of their own, so they
3006
+ // take nothing from the main one: the listing, `truncated` and the hash are
3007
+ // the ones the lister gave before handlers were listed.
3008
+ let handlerBudget = maxEntries;
3009
+ const takeHandler = () => {
3010
+ if (handlerBudget <= 0)
3011
+ return false;
3012
+ handlerBudget -= 1;
3013
+ return true;
3014
+ };
3015
+ // Four passes: every export and receiver, then every member name, then the
3016
+ // members of each handler's parameters (carrick#2674), then signatures.
2723
3017
  const names = [];
3018
+ const handlers = [];
2724
3019
  const fills = [];
2725
3020
  // What the package's default export names: a maker also exported by name
2726
3021
  // under this declaration is imported the same way, so it is read the same.
@@ -2736,7 +3031,7 @@ class DeclarationReader {
2736
3031
  if (!take())
2737
3032
  return;
2738
3033
  const home = this.homeOf(pkg, [target, ...typeSymbols(receiverType)]);
2739
- entry.receivers.push(this.outlineReceiver(pkg, receiverName, receiverType, home, take, names, fills));
3034
+ entry.receivers.push(this.outlineReceiver(pkg, receiverName, receiverType, home, take, takeHandler, names, handlers, fills));
2740
3035
  };
2741
3036
  receiverOf('export', type);
2742
3037
  for (const maker of this.makers(pkg, target, type)) {
@@ -2753,6 +3048,8 @@ class DeclarationReader {
2753
3048
  }
2754
3049
  for (const run of names)
2755
3050
  run();
3051
+ for (const run of handlers)
3052
+ run();
2756
3053
  for (const fill of fills)
2757
3054
  fill();
2758
3055
  surface.truncated = dropped;
@@ -2897,10 +3194,11 @@ class DeclarationReader {
2897
3194
  }
2898
3195
  /**
2899
3196
  * A receiver, now; its member names later (`names`), once every export and
2900
- * receiver of the package is listed; its call, construct and member
2901
- * signatures last (`fills`), once every name is.
3197
+ * receiver of the package is listed; the members of its handlers'
3198
+ * parameters next (`handlers`), once every name is; its call, construct and
3199
+ * member signatures last (`fills`), once every handler is.
2902
3200
  */
2903
- outlineReceiver(pkg, receiverName, type, home, take, names, fills) {
3201
+ outlineReceiver(pkg, receiverName, type, home, take, takeHandler, names, handlers, fills) {
2904
3202
  const receiver = { receiver: receiverName, members: [] };
2905
3203
  const holder = this.surfaceHolder(pkg, type, home);
2906
3204
  // The receiver's own call and construct signatures, as a claim with a
@@ -2927,11 +3225,102 @@ class DeclarationReader {
2927
3225
  const own = 'signatures' in callee;
2928
3226
  const member = { name: property.getName(), own, signatures: [] };
2929
3227
  receiver.members.push(member);
3228
+ handlers.push(() => {
3229
+ const listed = this.listHandlers(own ? callee.signatures : library, takeHandler);
3230
+ if (listed.length > 0)
3231
+ member.handlers = listed;
3232
+ });
2930
3233
  fills.push(() => (member.signatures = this.listSignatures(own ? callee.signatures : library, holder.type, take)));
2931
3234
  }
2932
3235
  });
2933
3236
  return receiver;
2934
3237
  }
3238
+ /**
3239
+ * The functions a member's signatures take, at a positional argument or at a
3240
+ * key of an options object, with the members their parameters declare. A
3241
+ * handler is listed once per place, from the first signature that states it.
3242
+ * A parameter that declares no member lists nothing, and so does a handler
3243
+ * none of whose parameters do.
3244
+ */
3245
+ listHandlers(signatures, take) {
3246
+ const listed = [];
3247
+ const seen = new Set();
3248
+ const add = (arg, key, slot) => {
3249
+ const place = JSON.stringify([arg, key ?? null]);
3250
+ if (seen.has(place))
3251
+ return;
3252
+ const params = this.listHandlerParams(slot, take);
3253
+ if (params.length === 0)
3254
+ return;
3255
+ seen.add(place);
3256
+ listed.push(key === undefined ? { arg, params } : { arg, key, params });
3257
+ };
3258
+ for (const signature of signatures) {
3259
+ const count = signature.getParameters().length;
3260
+ for (let arg = 0; arg < count; arg++) {
3261
+ const slot = this.keyParameterAt(signature, arg);
3262
+ if (slot === undefined || slot === VARIADIC)
3263
+ continue;
3264
+ if (this.handlerFailure(slot) === undefined) {
3265
+ add(arg, undefined, slot);
3266
+ continue;
3267
+ }
3268
+ if (!this.isObjectType(slot))
3269
+ continue;
3270
+ const keys = new Map();
3271
+ for (const view of this.keyViews(slot)) {
3272
+ for (const property of this.namedProperties(view)) {
3273
+ const type = this.checker.getTypeOfSymbol(property);
3274
+ if (!keys.has(property.getName()) && this.handlerFailure(type) === undefined)
3275
+ keys.set(property.getName(), type);
3276
+ }
3277
+ }
3278
+ for (const key of [...keys.keys()].sort(byName))
3279
+ add(arg, key, keys.get(key));
3280
+ }
3281
+ }
3282
+ return listed;
3283
+ }
3284
+ /** The parameters of a function type's first signature that declare members, each member counting against the cap. */
3285
+ listHandlerParams(handler, take) {
3286
+ const slot = this.throughConditional(this.throughConstraint(handler));
3287
+ if (slot === VARIADIC)
3288
+ return [];
3289
+ const signature = this.parts(slot)
3290
+ .flatMap(part => part.getCallSignatures())
3291
+ .find(each => each.getParameters().length > 0);
3292
+ if (!signature)
3293
+ return [];
3294
+ const listed = [];
3295
+ signature.getParameters().forEach((parameter, index) => {
3296
+ const type = this.checker.getTypeOfSymbol(parameter);
3297
+ if (this.namedProperties(type).length === 0 || !take())
3298
+ return;
3299
+ const members = [];
3300
+ this.listMembers(type, [], members, take);
3301
+ listed.push({ param: index, name: parameter.getName(), type: this.printed(type), members });
3302
+ });
3303
+ return listed;
3304
+ }
3305
+ /** A type's declared members by path, parents before children, each level sorted by name, down to `HANDLER_MEMBER_DEPTH`. */
3306
+ listMembers(type, prefix, into, take) {
3307
+ for (const property of sortedByName(this.namedProperties(type))) {
3308
+ if (!take())
3309
+ continue;
3310
+ const memberType = this.checker.getTypeOfSymbol(property);
3311
+ const path = [...prefix, property.getName()];
3312
+ const callable = this.librarySignatures(memberType, 'call').length > 0;
3313
+ into.push({
3314
+ path,
3315
+ type: this.printed(memberType),
3316
+ optional: (property.flags & ts.SymbolFlags.Optional) !== 0,
3317
+ function: callable,
3318
+ });
3319
+ if (!callable && path.length < HANDLER_MEMBER_DEPTH && this.isObjectType(memberType)) {
3320
+ this.listMembers(memberType, path, into, take);
3321
+ }
3322
+ }
3323
+ }
2935
3324
  listSignatures(signatures, holder, take) {
2936
3325
  const listed = [];
2937
3326
  for (const signature of signatures) {
@@ -3482,6 +3871,8 @@ class DeclarationReader {
3482
3871
  * union the builder cuts short keeps the members it made first, so the
3483
3872
  * listing orders every union before its own cut (`truncate`).
3484
3873
  */
3874
+ /** The longest member path a handler parameter's members are listed to (`['raw', 'url']`). */
3875
+ const HANDLER_MEMBER_DEPTH = 2;
3485
3876
  const PRINT_FLAGS = ts.NodeBuilderFlags.AllowUniqueESSymbolType |
3486
3877
  ts.NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope |
3487
3878
  ts.NodeBuilderFlags.IgnoreErrors |