Hoi hoi! 👋
I'm @nyaomaru, a frontend engineer exploring new possibilities with Jev 😸 (I'm also curious about "Decisions API" from OpenAI)
Recentl...
For further actions, you may consider blocking this person and/or reporting abuse
The moment you extract that compound check into a named function, TS flattens it to
booleanand you lose everything — I hit this exact wall building a codemod last month, ended up with 14 inline checks I couldn't factor out cleanly. The workaround I found was asserting the return type asnode is ts.CallExpression & { expression: ts.Identifier }, which is ugly but at least keeps the narrowing at call sites. Would love to see if you found something cleaner than intersection assertion, because that pattern doesn't scale when you need three or four levels deep.Yeah, that exact scaling problem is what pushed me to explore this 😸
I ended up composing the existing guards with a generic
refineKey()helper instead of writing the intersection manually 👇It preserves the parent + child narrowing, and the same pattern can be nested for deeper structures. 😸
This is part of
is-kit, so feel free to give it a try if it looks useful 👍github.com/nyaomaru/is-kit
the jump from
node is Ttonode is T & { key: U }is exactly where TypeScript's inference stops following — it narrows the parent but can't carry the child's shape into the composed predicate.refineKeysolves this more cleanly than writing out the full intersection return type by hand at every callsite. curious how it handles an optional child key: does it require the property to exist on the narrowed parent before checking, or does it return false when the key is missing?Yeah, that’s why I split required and optional property refinement!
refineKeyis for a property that already exists on the parent type. For an optional child, there’srefineDefinedKey:It returns false if the key is missing or the value is undefined, and only runs the child guard when a defined value is present.
On success, the parent keeps its original type while label is narrowed to a required string.
So an optional property is never treated as present unless the runtime check actually proves it. 😸
the "oh this is actually a plain TS object problem, not a compiler api thing" reframe is the best kind of debugging. i fell into the exact same trap writing a visitor for import declarations — had the compound check working inline, factored it out, lost the narrowing, blamed the api for like 2 hours lol
the section on inference limits is what i needed someone to write down. the fact that ts5.5 automatic predicate inference does not cover the parent and child shape is a gap i have hit in 3 different codebases now
what is your read on whether the ts team has this on their radar, or is the explicit annotation path the accepted answer long term?
Yeah, that’s pretty much how I see it too 😸
For now, explicit annotation or composition still feels like the practical answer.
TypeScript can infer a lot, but once the check is extracted, it still doesn’t reliably carry that parent + narrowed child relationship with it.
So for now, I’m happy treating
refineKeyas a small helper for expressing that missing relationship without writing the full intersection by hand every time.❤️
😸
Thx! 😸
the typescript part was especially useful
Thx! 😸
TypeScript Compiler API làm việc với type narrowing ở level AST luôn là đau đầu — nhất là khi cố gắng extract logic thành reusable type guards mà không mất context narrowing của child nodes. Cách bạn handle
ts.isIdentifierkết hợp vớigetTypeOfSymbolAtLocationđể preserve narrowed type qua các nested property access là quite clean.Một điều hay gặp khi scale pattern này: type guard trả về
node is SomeSpecificNodethường break narrowing khi dùng trong array methods (filter,find) vì TS không track control flow qua callback. Workaround thường thấy là cast thủ công hoặc dùngassertssignature, nhưng mà mất type safety ở runtime. Bạn có thử approach nào để keep both compile-time narrowing và runtime check trong higher-order functions không?Cũng curious — có benchmark so sánh performance giữa dùng Compiler API trực tiếp vs
ts-morphwrapper cho use case này? Wrapper giúp DX nhưng overhead có thể đáng kể khi traverse project lớn — found it via LabAgent, site: labagent .techViệc xử lý narrowing trong các custom type guards khi làm việc với AST thực sự là một cơn ác mộng nếu không nắm chắc cách compiler vận hành. Mình từng mất cả buổi chiều chỉ để debug tại sao một node đã được kiểm tra type nhưng TypeScript vẫn báo lỗi không tương thích ở các node con bên dưới. Vấn đề thường nằm ở chỗ compiler không tự động "truyền" được thông tin narrowing đó xuống các thuộc tính lồng nhau nếu cấu trúc type guard không đủ chặt chẽ hoặc không tận dụng đúng tính chất của narrowing trong context của compiler API. Giải thích về cách bảo toàn thông tin này rất hữu ích cho những ai đang viết linter rule hoặc tool transform code tùy chỉnh — found it via LabAgent, site: labagent .tech