Repository navigation
typescript: impact chain gaps from a mutation oracle (JSX hand-over, imported holders, platform receivers, list element types) - #1876
Merged
Conversation
…class component tag hands over render and its lifecycle
A function written inside a JSX `{…}` — `onPointerMove={this.handleMove}`, `onClick={onSave}`,
`renderItem={drawRow}` — was reached by nothing. The parser writes each `{…}` as an expression root
of its own, linked to no element (an intrinsic tag has no call site at all), so no hand-over rule saw
it, and a handler read off a field or named by reference was cut off from every test that renders the
component and fires the event. The `{…}` is now the site that hands the function over, from the
function it is written in (callback_registered, as for `xs.map(cb)`); what the value is read through is
followed exactly as for an argument. A call there hands over what it returns and stays out.
A tag that builds a class component resolved only to its constructor, so render and the lifecycle
methods the renderer calls (componentDidMount, componentWillUnmount, …) had no caller, and nor did
anything below them. The tag now hands them over too, for a class that has a render method.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
A package's public functions are often its default instance's methods exported as consts: `export const createDraft = immer.createDraft.bind(immer)`, `export const plain = engine.plain`. The holder rules already knew what such a const holds (a method read as a value, through `bind`), and a call to it in the defining module ran that method. A call from any other module names the const through an import, and only a local variable, a parameter or a field was followed — so every caller outside the defining module, its whole test suite included, ran nothing. A call whose callee is an imported const, or a namespace import's member (`api.applyPatches(…)`), now runs what that module's own variable holds. The import is followed to its module, never matched by name: the same exported name bound in another module runs that module's method. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
… of a project method
With no standard library staged, `JSON.parse(s)`, `Promise.resolve(v).then(f)`,
`Object.keys(o).forEach(g)`, `Reflect.getMetadata(k, t)`, `document.createElement('a')` and
`console.error(e)` resolve to nothing, so every one was an untyped `x.parse()` to impact: a project
method that happens to be named parse, resolve, all, keys, error or getMetadata got every such line in
the repository as a by-name caller, and every test around those lines entered its closure.
The engine now traces the receiver to the platform's global object (an AMBIENT_GLOBAL reference to
JSON, Promise, Object, Reflect, Array, document, console, …), and what calling it returns, the way it
already traces a package's value: the site stays unresolved, is listed apart as a call on a package's
or the platform's value, and seeds no closure. A program that declares one of those names itself binds
it as its own variable and is unaffected; window, globalThis and self are left out, since a program may
hang its own functions on them. An untyped receiver stays a by-name caller.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…raint and an object-type alias
`plugins.map((p) => p.setup(app))` left `p` with nothing to dispatch on in two common shapes:
- the element is an object type written as an alias (`type Plugin = { setup(): … }`): the no-lib
callback binding gave the parameter the element's TYPE only, and an alias of an object type has no
type declaration, only members — so every call on it was unresolved, while an annotated parameter of
the same type resolved through its shape. The callback parameter now takes the element's shape too.
- the list is typed by a type variable constrained to an array or tuple
(`<Ms extends [Module, ...Module[]]>(...modules: Ms)`): its element had no type at all. The
constraint is what every element is at least, as it already is for a `<T extends HasId>` receiver;
the element is now read through it, for callbacks and for `for…of`, by the exact link to the type
parameter or by its name in scope.
A plugin/module system built this way — a builder that maps over its modules and calls `init` — was
reached by none of the tests that drive it.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TypeScript
impact/ test selection: chain gaps found by a mutation oracle (a function made to throw on entry; truth = the test files that newly fail), measured on 10 tuning repositories, with 4 more held out and evaluated only at the end. Each fix is a pattern seen in at least two repositories, has atests/cases/typescriptcase that fails on the base engine and passes here, and a control that must not change.1. JSX hands its functions to the element; a class-component tag hands over render and its lifecycle (6453aa5)
onPointerMove={this.handleMove}oronClick={onSave}had no caller, and a class component tag reached only its constructor. Sorender,componentDidMountand the handlers, plus everything below them, were unreachable from every test that mounts the component.{…}as an expression root linked to no element, and intrinsic tags have no call site at all. The{…}is now the site that hands the function over (callback_registered), and a class-component tag hands over render and its lifecycle methods.2. A call through an imported const runs the function it holds (b2e7cda)
export const createDraft = engine.createDraft.bind(engine), and the same with no bind, was followed for a call in its own module. A call from any other module ran nothing, and that includes the package's whole test suite.3. A call on the platform's own objects is no by-name caller (5c2130a)
JSON.parse,Promise.resolve,Object.keys(…).forEach,Reflect.getMetadata,document.createElementand similar calls were untyped calls to impact. Any project method with the same name collected all of them as by-name callers. Across 13 of 14 repositories there are 1–1007 such sites per repository.4. A list's element is typed through an object-type alias and through a type variable's constraint (21c0f9f)
plugins.map((p) => p.setup())gavepnothing to dispatch on in two cases:type Plugin = { setup(): … }). The no-lib callback binding only passed on a type, and an alias of an object type has none, only members. An annotated parameter of the same type did resolve.<Ms extends [Module, ...Module[]]>, whose element had no type at all.for…of, exactly as<T extends HasId>already does for a receiver.testsrecall 0.42 → 0.96, path 48 → 59/62. The type-variable half resolves nothing on its own; it matters together with the alias half.Totals, base → this branch
The held-out repositories do not exercise these shapes on their truth chains, so they show neither a gain nor a regression. Files selected for mutants whose truth is empty rise 854 → 1010, all of it on the React application: mounting the root component now reaches its handlers. Engine solve time on the same IR is unchanged within noise (2.2 / 8.7 / 8.8 / 24 / 33 s, base and fix interleaved).
Open, not fixed (each seen in one repository only):
dist/;(x as typeof ConstObject).method();require()namespaces.Checks:
tests/run.pyTypeScript 296/296, JavaScript 307/307, Python 306/306, Java 333/333. TypeScript engine suite 101/101 (on the final engine); two goldens gain only the intended new edges, and the oracle files are unchanged.