Repository navigation
TS/JS: this.field.m() is not resolved when this.field = new X() is assigned outside the constructor #4473
Description
Summary
Since v0.9.91 (990ab1b7, addressing #4294) the receiver of this.field.m() is typed from the caller's own class and
its bases. The constructor is scanned for this.f = new X() and this.f = <typed parameter>, but the same
assignment in any other method, including a static one, is not recorded, so the call stays unresolved.
This is not a regression: v0.9.90 resolved none of the three shapes below, v0.9.91 resolves the constructor one.
The commit message scopes the constructor deliberately (precision measured as "0 wrong" on TypeORM/NestJS), so this is
a request to extend that scope, not a bug report. Python already resolves the same shape on v0.9.91:
def setup(self): self.repo = RepoA() followed by self.repo.save() in another method produces the calls edge.
Reproduction
Run from a clean checkout of the tag. Each case writes two files and checks for a calls edge .go() -> .save():
import pathlib, tempfile
from graphify.extract import extract
REPOS = "export class RepoA { save(): number { return 1; } }\n"
SVC = {
"constructor": "constructor() { this.repo = new RepoA(); }\n go() { return this.repo.save(); }",
"method": "init() { this.repo = new RepoA(); }\n go() { return this.repo.save(); }",
"static": "static init() { this.repo = new RepoA(); }\n static go() { return this.repo.save(); }",
}
for name, body in SVC.items():
d = pathlib.Path(tempfile.mkdtemp())
(d / "repos.ts").write_text(REPOS, encoding="utf-8")
(d / "svc.ts").write_text(
'import { RepoA } from "./repos";\nexport class Svc {\n ' + body + "\n}\n", encoding="utf-8")
r = extract([d / "repos.ts", d / "svc.ts"], cache_root=d, parallel=False)
label = {n["id"]: n["label"] for n in r["nodes"]}
hit = any(e["relation"] == "calls" and label[e["source"]] == ".go()" and label[e["target"]] == ".save()"
for e in r["edges"])
print(f"{name:12} go() -> RepoA.save(): {'resolved' if hit else 'NOT resolved'}")| case | v0.9.90 | v0.9.91 |
|---|---|---|
constructor() { this.repo = new RepoA(); } |
NOT resolved | resolved |
init() { this.repo = new RepoA(); } |
NOT resolved | NOT resolved |
static init() { this.repo = new RepoA(); } |
NOT resolved | NOT resolved |
Expected: the init() and static init() cases resolve like the constructor one.
Related
- [Bug]: TS: a class field's type leaks to every same-named receiver in its file, so
A.runbindsthis.repo.save()toRepoB.save#4294 (open): the report behind the class-scopedthis.fieldlookup this builds on (990ab1b7, v0.9.91), which
readsthis.f = new X()in the constructor only. fix(ts): scope class-field receiver types to the field's own class #4297 (closed, not merged) proposed the same scoping. - feat(ts): type
this.fieldreceivers from class field declarations #4231 (closed, not merged): typedthis.fieldfrom field declarations only. A different construct: it does not
read assignments at all.
Suggested direction
Collect this.f = new X() from every method of the class into the table the constructor pass already fills
(static methods under the static key), keeping the current precedence: a declared field type, then the
constructor-derived one, then a method assignment. If one class assigns two different types to the same
field, record neither (an assignment in a nested plain function, where this is rebound, should not count).
With exactly this change on top of v0.9.91 the repro above resolves all three cases; a field assigned two different
classes in one class resolves nothing; an assignment in a nested function is ignored; a declared field type wins.
Activity
github-actions commentedon Oct 15, 2026
Thanks for opening this issue, @sub4biz. A maintainer will take a look soon.
If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.
To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.
FolatheDuckofDuckingburg commentedon Oct 15, 2026
Your wish is my command
I'm opening a PR
omsenjalia commentedon Oct 18, 2026
This looks fixed on the
v8branch by 6948da8e (test(ts): cover method-assigned field receivers). Could a maintainer confirm and close?