Kyle Into
90d · built 2026-11-10
Performance
What Kyle Into shipped in the selected window, measured in ETV, and how it compares with the 90 days before it.
Effective capacity
+4.4engineers
delivers like 5.4 (5.4x pre-AI)
Output (ETV)
41.4ETV
+300.6% vs 10.3 prior
Features share
31.7%
+15.0 pp vs prior window
Fixes share
20.7%
−11.1 pp vs prior window
Work mix
31.7% Features32.2% Maintenance12.4% Tests3% Docs20.7% Fixes
213 commits over 90 days, ending 2026-11-10.
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 24 %
- By Features share
- Top 69 %
Daily performance
Daily ETV, stacked by Features, Maintenance, Tests, Docs and Fixes.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 10 by ETV in the last 90 days.
- 15.6ETVUpgrade typeshed Summary: Stack context: Upgrade pyrefly's bundled typeshed, and fix the pyrefly problems that the upgrade exposes. - This diff upgrades the bundled typeshed from `a7bcce8` (2026-07-07) to upstream `main` at `7030b26`, and updates the test expectations that depend on the stubs. - The bundled stubs are more than three months old. Upstream has stub fixes that users report as pyrefly bugs, for example the 3.15 `frozendict` fixes. The diffs below this one fix the pyrefly problems that the new stubs expose. - The test updates follow from the new stubs: moved line numbers in `builtins.pyi` and `typing.pyi`, the removed `builtins.ellipsis` alias, and the new `_SupportsReversed` protocol. The `unused-coroutine` test for a Protocol `Coroutine` now uses the bundled stubs directly, so it moves from a custom-typeshed test in `test/state.rs` to a `testcase!`. I ran `crates/pyrefly_bundled/update.py` with docify enabled. The script downloaded the archive URL directly, because the GitHub API rate limit blocks the default `main` lookup through fwdproxy. Reviewed By: stroxler Differential Revision: D126992287 fbshipit-source-id: 903c56ea456a8c65682a8eecd3c4de2998bb50d9github.com-facebook-pyrefly · 75687253 · 2026-10-27
- 0.9ETVLet a build system supply per-target type checking config Summary: Stack context: A build system that owns a repository’s Python layout should be able to own its type checking settings too, so that users of a custom build system do not have to keep a parallel `pyrefly.toml` in sync with it. This stack lets the build system response carry named type checking configs, point each target at one, and name a default for everything else. External users with custom build systems (e.g. Arista Networks) need the build system to be the single source of truth for both import resolution and type checking settings. The response can already describe what a target’s sources are, but not how they should be checked. - Adds a top-level `configs` map of named type checking settings, and a per-target `config` naming one of them. Settings resolve target config → sub-config → root config → preset defaults; error settings merge by kind across those levels. - Targets name a shared config rather than spelling one out inline, because in a large repo the same handful of settings applies to thousands of targets. Naming them keeps the response small, and lets each config be deserialized into a `ConfigBase` at most once and cached on the `ConfigFile` — which matters because every per-file setting lookup consults the owning target’s config. The cache is cleared whenever the source database reports that it changed. Target configs accept the settings of a `ConfigBase`, the same ones a sub-config can override. Notably that does not include `preset`, which lives on `ConfigFile`. Reviewed By: connernilsen Differential Revision: D107113346 fbshipit-source-id: 6d0c11b9ba763b7864fe83f5f01e77e04df9e97agithub.com-facebook-pyrefly · 3d6cb73c · 2026-09-09
- 0.7ETVResolve `Self` to the TypeVar when subscripting a class-bounded TypeVar (#5258) Summary: Pull Request resolved: https://github.com/facebook/pyrefly/pull/5258 Subscripting a value whose type is a TypeVar bounded by a class (`seq[0:1]` with `S: SupportsSlice`) inferred the bound instead of `S` when `__getitem__` returns `Self`. Calling the method explicitly (`seq.__getitem__(slice(0, 1))`) already inferred `S`. The subscript path handled a bounded TypeVar by subscripting its bound, so `__getitem__` was looked up and called with the bound as the receiver, and `Self` resolved to the bound. Binary operators don't have this problem because they call the dunder on the TypeVar itself. For a TypeVar bounded by a class whose subscripts use the plain `__getitem__` call, we now call `__getitem__` on the TypeVar, the same way an explicit method call does. Attribute lookup on a bounded TypeVar binds `Self` to the TypeVar. Bounds with builtin subscript handling (lists, tuples, shaped arrays, `nn.ModuleDict`) still subscript the bound, so their precise indexing is kept. fixes https://github.com/facebook/pyrefly/issues/5257 Reviewed By: rchen152 Differential Revision: D125829160 fbshipit-source-id: 63c81f74c489a66f4d9ca82ffca62863a9741fe2github.com-facebook-pyrefly · 720fd6ae · 2026-10-27
- 0.6ETVIndex parent methods by class and name Summary: Stack context: Fix go-to-definition and find-references on class members after the defining file shifts (https://github.com/meta/pyrefly/issues/5485). - Problem: find-references on `Base.method` in `base.py` misses the override `Child.method` in `child.py` after a blank line is inserted above `Base.method` and the file is saved. `Index::parent_methods_map` of `child.py` stores the range of `Base.method` from when `child.py` was last solved. A whitespace-only change does not change the answers of `base.py`, so `child.py` is not rechecked, and the stored range no longer matches the new definition range. Find-implementations and the override code lens read the same map, so they miss the same overrides. - Fix: the map now stores the parent class and the method name (`IndexedAttribute`), like the attribute reference maps. The queries look up the current range of the parent method in the metadata of its module, with the new `IndexedAttribute::current_range`. Fixes https://github.com/meta/pyrefly/issues/5485 ___ landed-with-radar-review Differential Revision: D129782930 fbshipit-source-id: 3314ee10fd92879052b6be7f46644f05f0c91ec1github.com-facebook-pyrefly · 03cfcd13 · 2026-11-06
- 0.6ETVKeep reliable variance evidence for writable callbacks Summary: Variance reached through a recursively referenced generic can be incomplete. Combining that fallback with direct evidence lets the recursive result override method and field uses. Writable callable fields were also treated as covariant even though both reads and writes make them invariant. Track reliable and fallback evidence separately, prefer direct evidence when available, and infer invariant variance for writable callable fields. This preserves generic descriptor binding while rejecting callback replacements that remove valid calling forms. Third of three commits split from the original change. Part of https://github.com/facebook/pyrefly/pull/4878 by Toby Harradine <tobyh@canva.com>. Reviewed By: stroxler Differential Revision: D119739486 fbshipit-source-id: 97f5087eda2c7a4bcd11d271a67f4783220d4c9egithub.com-facebook-pyrefly · 47f59fca · 2026-09-15
- 0.6ETVMark the truthy operand of `or` so that a falsy narrow removes it (#5514) Summary: Pull Request resolved: https://github.com/meta/pyrefly/pull/5514 Stack context: pyrefly keeps the falsy operand of `x and y` in the result after a truthy narrow, so `genre = author and author.genre; if genre:` still includes `Author`. - Problem: for `override: Config | None`, `config = override or default` has type `Config | Settings`, and `if not config:` keeps `Config`. `or` returns `override` only when it is truthy, so a falsy `config` is always `default`. This is the `or` form of the `and` problem. - Fix: add an internal `Truthy` type, the counterpart of `Falsy`. For each operand of `or` except the last, intersect every class instance whose truthiness is not statically known, except `int`, `str` and `bytes`, with `Truthy`: `override or default` is `(Config & Truthy) | Settings`. `IsFalsy` removes `Config & Truthy`. Like `Falsy`, `Truthy` is hidden from display and `assert_type`, so `override or default` still shows as `Config | Settings`. `int`, `str` and `bytes` are left unmarked: a falsy narrow already gives `0`, `""` and `b""` for them, and a literal default such as `name or ""` must still collapse to `str`, which py2hack needs. - A marked type can become the inferred type of an attribute or a container element: `self.base = base or Sub()` gives `self.base` the type `(Base & Truthy) | Sub`. So a truthiness marker does not restrict assignability: `x` is assignable to `A & Truthy` if it is assignable to `A`. Otherwise a later `self.base = base` is a false `bad-assignment`. - A union sorts a marked member as its unmarked type. An intersection sorts before every class, so otherwise the marker changes the member order of a union in error messages (`bool | list[str]` becomes `list[str] | bool`), and first-use inference of `out.extend(a or b)` with `a: list[Any]` and `b: list[bytes]` solves `out` from `list[Any]` first. This also applies to `Falsy`, and restores the union order that `test_boolean_and_object_operand` expected before `Falsy`. - A unary operator on an intersection looks up the dunder method on the intersection, as attribute access does. Otherwise `~((x and y) or C.NONE)` for a `Flag` `C` is a false `unsupported-operation`. CI error differentials: - mypy_primer (against the parent diff): +1712 / -1713. 1699 pairs are the same error with the union members back in their order before `Falsy`. 12 `implicit-bool` errors show a narrower type, because a falsy narrow removes the truthy operand of `or` (for example, zope.interface `ModuleType | None | bool` becomes `None | bool`): intended. schemathesis `_schema.py:3864` is fixed: the falsy value of `(only_required or not ...)` in the `and` chain is no longer the `dict`, so the chain is `Any | bool`: true fix. steam.py `state.py:488` is the same true-positive `bad-argument-type`; the argument shows as `UserMessage[ClientUser | Friend | User]` instead of `UserMessage`. - pytorch: 5 errors fixed, all `Unary - is not supported on Tensor & float` (torch/optim): true fixes from the unary-operator change. - instagram-server: no differential. Reviewed By: rchen152 Differential Revision: D129234807 fbshipit-source-id: d1c08e65114d575431aa34a4ab86b7b4a52c7089github.com-facebook-pyrefly · 9d121a0d · 2026-11-04
- 0.5ETVMark the falsy operand of `and` so that a truthy narrow removes it Summary: Stack context: pyrefly keeps the falsy operand of `x and y` in the result after a truthy narrow, so `genre = author and author.genre; if genre:` still includes `Author`. - Problem: for `author: Author | None`, `genre = author and author.genre` has type `Author | Genre | None`, and `if genre:` narrows it to `Author | Genre`. `and` returns `author` only when it is falsy, so a truthy `genre` is always a `Genre`. The type does not record that this `Author` is falsy: `Author` is not final, so a subclass can define a falsy `__bool__`, and the truthy narrow must keep a plain `Author`. The same happens with `dict` and other classes whose truthiness is not statically known, after an assignment or a walrus. - Fix: add an internal `Falsy` type. For each operand of `and` except the last, intersect every class instance whose truthiness is not statically known with `Falsy`: `author and author.genre` is `(Author & Falsy) | Genre | None`. An intersection is falsy if any member is, so `IsTruthy` removes `Author & Falsy`. Attribute access and assignability still use `Author`, so the falsy-subclass case stays sound. - `Falsy` only serves narrowing, so it is hidden from users: hovers, `reveal_type` and error messages show `Author & Falsy` as `Author`, and `assert_type` compares types with the marker removed. The type table that py2hack reads also shows the marked type without the marker, since py2hack cannot translate an intersection. `object & Falsy` keeps `object` when it is simplified, so `Falsy` never appears outside an intersection. - `Falsy` has no annotation syntax. D129227950 makes `pyrefly infer` write an intersection as its fallback type, so it writes `Author | Genre | None`. Reviewed By: rchen152 Differential Revision: D129227012 fbshipit-source-id: 0e0568a8e7c719e4da6512a6365578fc1bc4bee2github.com-facebook-pyrefly · a43357a5 · 2026-11-04
- 0.5ETVCache flat symbols for first-party modules Summary: Workspace features need nested declarations, but the export table intentionally contains only top-level names and retaining full syntax trees for every indexed module is unnecessarily expensive. Build a compact, source-ordered table of supported declarations with parent indices, and retain it only for first-party modules kept for indexing. The table preserves distinct declaration sites and enough scope information to distinguish methods from functions without retaining or reparsing the AST. This remains a small syntax-only pass because the existing passes have different data and lifetimes. Export collection stops at scope boundaries and merges logical bindings, so it cannot preserve nested source declarations, duplicate sites, or parent identity. Document-symbol collection runs only for an LSP request, depends on a retained AST, and builds presentation data such as owned strings, comment sections, and converted LSP ranges. The compact pass runs before AST eviction for indexed unopened files and reuses shared syntax rules such as `Ast::expr_lvalue` and `is_constant_name`. Reviewed By: grievejia Differential Revision: D115173206 fbshipit-source-id: f63325b33cea96cc23b6b0c871f1a94f444b1ef2github.com-facebook-pyrefly · 5b392caf · 2026-09-10
- 0.5ETVAdd a wasm32-wasip1 binding layer for the sandbox Summary: Stack context: Build the Pyrefly sandbox for `wasm32-wasip1`, so Pyrefly has one wasm target and WASI hosts (https://github.com/meta/pyrefly/pull/5436) need no `target_os = "wasi"` code. The first four diffs fix sandbox bugs that do not depend on the target, so they can land first. - What: `lib.rs` now exports a small C ABI (`pyrefly_new`, `pyrefly_call`, `pyrefly_drop`, `pyrefly_version`, `pyrefly_alloc`, `pyrefly_free`) that passes JSON in guest memory. The new `pyrefly_wasm.js` provides the WASI imports and wraps these exports in the same `State` class and `version()` function that `wasm-bindgen` generates. The existing `wasm-bindgen` code moves unchanged to `bindgen.rs`. - Why: `wasm-bindgen` compiles out every export on WASI, so a wasip1 sandbox needs its own binding layer. This diff only adds it: the sandbox still builds for `wasm32-unknown-unknown` with `wasm-bindgen`, and the next diff switches the target and deletes `bindgen.rs`. - Protocol (the contract to check `lib.rs` and `pyrefly_wasm.js` against): - Wasm exports take and return only numbers, so strings travel as UTF-8 bytes in the module's memory. - Request: the host calls `pyrefly_alloc(len)`, writes the bytes at the returned pointer, and passes `(ptr, len)` to `pyrefly_new` or `pyrefly_call`. The callee takes ownership and frees the request. - Response: every export except `pyrefly_alloc` and `pyrefly_drop` returns one `u64`, `(ptr << 32) | len`, which is a `BigInt` in JavaScript. The bytes are the JSON `{"ok": value}` or `{"err": message}`. The host reads them, then calls `pyrefly_free(ptr, len)`. `State` turns `err` into a thrown `Error`. - `pyrefly_call` takes the JSON `{"method": "<State method name>", "args": ...}`, which deserializes into the `Request` enum: an array for several arguments, a bare value for one argument, and no `args` for none. - `pyrefly_new` returns a playground handle, which indexes a table in the module. Handles are never reused. `State.free()` calls `pyrefly_drop(handle)`, and a `FinalizationRegistry` calls it when JavaScript collects a `State` that was not freed. - Details: - Wasm passes `i32` and `u64` values to JavaScript as signed numbers, so `pyrefly_wasm.js` makes every pointer unsigned. Without this, a pointer at or above 2 GiB makes each call fail. - A WASI import that `pyrefly_wasm.js` does not implement returns `ENOSYS`, so a new import in the module does not make instantiation fail. - A panic aborts, which traps, and the instance is not usable after a trap, as with `wasm-bindgen`. A later diff in this stack makes a new instance after a trap. - `State` converts arguments as `wasm-bindgen` does: numbers through `| 0`, flags through `Boolean`, and lone surrogates in strings to U+FFFD, which `serde_json` would reject. It uses a regular expression for the surrogates, because browsers before Safari 16.4 and Firefox 119 have no `String.prototype.toWellFormed`. - `init` accepts a URL, a `Response`, the module bytes, or a compiled `WebAssembly.Module`. For a response with the `application/wasm` type, it uses `WebAssembly.compileStreaming`, which compiles while the module downloads and lets the browser cache the compiled code. It throws a clear error when the fetch fails. Reviewed By: rchen152 Differential Revision: D130063331 fbshipit-source-id: 6a1f4a3064c287a2c2e62322df6f537f932f543cgithub.com-facebook-pyrefly · 4a0e22e9 · 2026-11-06
- 0.5ETVIndex cross-module attribute references by class and name Summary: Stack context: Fix go-to-definition and find-references on class members after the defining file shifts (fixes https://github.com/meta/pyrefly/issues/5485). - Problem: find-references on `helper` in `lib.py` misses the uses in `user.py` after a blank line is inserted above `helper` and the file is saved. The reference index of `user.py` stores the definition range of `helper` from when `user.py` was last solved. A whitespace-only change does not change the answers of `lib.py`, so `user.py` is not rechecked, and the stored range no longer matches the new definition range. Call hierarchy reads the same index, so it misses the same callers. - Fix: the index now stores the class and the attribute name (`IndexedAttribute`) for attribute and constructor references. The query looks up the definition range in the current class metadata of the defining module, the same way module-level names are already looked up in the current exports. - Why the stored class index stays valid: `Class` equality includes `ClassDefIndex`, so an edit that renumbers the classes of `lib.py` changes its exported types and class answers, and `user.py` is rechecked. Only edits that keep every answer equal, such as whitespace, skip the recheck, and those edits keep the class indexes. Reviewed By: samwgoldman Differential Revision: D129759626 fbshipit-source-id: 656d7e52878cb6a5fb4728fef5b43c2098fbb292github.com-facebook-pyrefly · 06539708 · 2026-11-06