mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(python): resolve ClassName() calls to __init__
A Python construction call resolved to the Class node, so a user __init__ never received a caller from ClassName(...) and the dead-code view reported live constructors as unreferenced. When the called name is the class itself, resolve it to the __init__ found on the class or, depth-first, its declared bases (the lookup super().__init__() already uses), with a new lsp_constructor_init strategy that joins the textual call site through its reason. Classes with no user __init__ keep the Class target (lsp_constructor), as does a builtins.* __init__. Calls through an instance (f = Foo(); f()) are unchanged, since they dispatch to __call__. This changes the documented Class-target contract for classes that define __init__; the three tests that pinned it are updated. Refs #1642 Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
This commit is contained in:
6 files changed
+332
-11
No files matched your search
@@ -3249,7 +3249,26 @@ static void py_emit_call_for(PyLSPContext *ctx, TSNode call_node) {
|
||||
py_emit_resolved_call_reason(ctx, qn, "lsp_import_alias", 0.85f, fname,
|
||||
call_node);
|
||||
} else if (qn && cbm_registry_lookup_type(ctx->registry, qn)) {
|
||||
py_emit_resolved_call(ctx, qn, "lsp_constructor", 0.85f, call_node);
|
||||
/* ClassName() runs a user __init__ found on the class or,
|
||||
* depth-first, a declared base (the same walk
|
||||
* super().__init__ uses, not C3 MRO). Retarget only when
|
||||
* this binding IS the class being called. An instance
|
||||
* (`f = Foo(); f()`) keeps the class row: that call
|
||||
* dispatches to __call__, and a reason-join would invent
|
||||
* a Foo.__init__ edge. A builtins.* __init__ is not a
|
||||
* project constructor, so the class target stays. */
|
||||
const CBMRegisteredFunc *fi = NULL;
|
||||
if (qn_short && strcmp(qn_short, fname) == 0) {
|
||||
fi = py_lookup_attribute(ctx, qn, "__init__");
|
||||
}
|
||||
if (fi && fi->qualified_name &&
|
||||
strncmp(fi->qualified_name, "builtins.", sizeof("builtins.") - 1) != 0) {
|
||||
py_emit_resolved_call_reason(ctx, fi->qualified_name,
|
||||
"lsp_constructor_init", 0.85f, fname,
|
||||
call_node);
|
||||
} else {
|
||||
py_emit_resolved_call(ctx, qn, "lsp_constructor", 0.85f, call_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -388,6 +388,7 @@ static inline bool cbm_pipeline_invocation_reason_join_strategy(const char *stra
|
||||
strcmp(strategy, "lsp_method_ref_ctor_synth") == 0 ||
|
||||
strcmp(strategy, "lsp_dict_dispatch") == 0 ||
|
||||
strcmp(strategy, "lsp_import_alias") == 0 ||
|
||||
strcmp(strategy, "lsp_constructor_init") == 0 ||
|
||||
strcmp(strategy, "php_method_dynamic") == 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,10 @@
|
||||
* PYTHON STRATEGY INVENTORY — every literal "lsp_..." emitted by py_lsp.c
|
||||
* (grep '"lsp_' internal/cbm/lsp/py_lsp.c), with its keying site:
|
||||
* lsp_direct (py_lsp.c:1631) module-local f()
|
||||
* lsp_constructor (py_lsp.c:1624) ClassName() where the name is a
|
||||
* NAMED type in scope
|
||||
* lsp_constructor ClassName() with no user __init__ → the Class
|
||||
* lsp_constructor_init ClassName() with a user __init__ (on the class
|
||||
* or a declared base) → that __init__. The textual
|
||||
* callee leaf stays the class name; reason joins it.
|
||||
* lsp_method (py_lsp.c:1731) obj.method() on a NAMED-typed
|
||||
* receiver (covers self.other())
|
||||
* lsp_super (py_lsp.c:1693) super().method() resolved on a
|
||||
@@ -340,13 +342,24 @@ static const char kPyDirect[] =
|
||||
"def caller(v):\n"
|
||||
" return helper(v)\n";
|
||||
|
||||
/* lsp_constructor — ClassName() where the name is a NAMED type in scope
|
||||
* (py_lsp.c:1620-1624: cbm_scope_lookup yields a NAMED type → emit constructor
|
||||
* edge to the class QN). */
|
||||
/* lsp_constructor — ClassName() where the class has no user __init__.
|
||||
* The edge stays on the Class node. A class that defines __init__ is
|
||||
* lsp_constructor_init below; strstr would otherwise treat that strategy
|
||||
* as a hit for "lsp_constructor". */
|
||||
static const char kPyConstructor[] =
|
||||
"class Widget:\n"
|
||||
" def label(self):\n"
|
||||
" return 1\n"
|
||||
"def caller():\n"
|
||||
" return Widget()\n";
|
||||
|
||||
/* lsp_constructor_init — ClassName() resolves to the user __init__
|
||||
* (issue #1642). reason carries the class-name leaf so the pipeline join
|
||||
* still matches the textual call. */
|
||||
static const char kPyConstructorInit[] =
|
||||
"class Widget:\n"
|
||||
" def __init__(self):\n"
|
||||
" pass\n"
|
||||
" self.ready = True\n"
|
||||
"def caller():\n"
|
||||
" return Widget()\n";
|
||||
|
||||
@@ -538,6 +551,10 @@ TEST(repro_lsp_py_constructor) {
|
||||
return assert_lsp_strategy("main.py", kPyConstructor, "lsp_constructor");
|
||||
}
|
||||
|
||||
TEST(repro_lsp_py_constructor_init) {
|
||||
return assert_lsp_strategy("main.py", kPyConstructorInit, "lsp_constructor_init");
|
||||
}
|
||||
|
||||
TEST(repro_lsp_py_method) {
|
||||
return assert_lsp_strategy("main.py", kPyMethod, "lsp_method");
|
||||
}
|
||||
@@ -623,6 +640,7 @@ SUITE(repro_lsp_go_py) {
|
||||
|
||||
RUN_TEST(repro_lsp_py_direct);
|
||||
RUN_TEST(repro_lsp_py_constructor);
|
||||
RUN_TEST(repro_lsp_py_constructor_init);
|
||||
RUN_TEST(repro_lsp_py_method);
|
||||
RUN_TEST(repro_lsp_py_super);
|
||||
RUN_TEST(repro_lsp_py_super_init);
|
||||
|
||||
@@ -826,14 +826,31 @@ TEST(es_decorates_attribute_csharp) {
|
||||
* resolves in the registry. pass_usages.c handles this.
|
||||
* ══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Python constructor syntax is a CALLS edge to the materialized Class node. */
|
||||
/* Python ClassName() with a user __init__ is a CALLS edge to that method.
|
||||
* The Class node no longer receives the instantiation edge. A class with no
|
||||
* user __init__ still targets the Class (lsp_constructor). */
|
||||
TEST(es_usage_crossfile_python) {
|
||||
static const ES_LangFile f[] = {
|
||||
{"models.py",
|
||||
"class User:\n def __init__(self, name):\n self.name = name\n"},
|
||||
{"main.py",
|
||||
"from .models import User\n\n\ndef create_user(name):\n return User(name)\n"}};
|
||||
ASSERT_EQ(es_exact_edge_by_name(f, 2, "CALLS", "create_user", "User"), 1);
|
||||
ASSERT_EQ(es_exact_edge_by_name(f, 2, "CALLS", "create_user", "__init__"), 1);
|
||||
ASSERT_EQ(es_exact_edge_by_name(f, 2, "CALLS", "create_user", "User"), 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Issue #1642 repro: build() -> Embedder.__init__, not the Embedder class. */
|
||||
TEST(es_calls_python_constructor_init) {
|
||||
static const ES_LangFile f[] = {
|
||||
{"main.py", "class Embedder:\n"
|
||||
" def __init__(self, model=\"x\"):\n"
|
||||
" self.model = model\n"
|
||||
"\n"
|
||||
"def build():\n"
|
||||
" return Embedder(model=\"y\")\n"}};
|
||||
ASSERT_EQ(es_exact_edge_by_name(f, 1, "CALLS", "build", "__init__"), 1);
|
||||
ASSERT_EQ(es_exact_edge_by_name(f, 1, "CALLS", "build", "Embedder"), 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -982,6 +999,7 @@ SUITE(edge_structural) {
|
||||
/* ── FAMILY 7: USAGE cross-file ─────────────────────────── */
|
||||
/* Uncertain: depends on USAGE extraction for each language. */
|
||||
RUN_TEST(es_usage_crossfile_python);
|
||||
RUN_TEST(es_calls_python_constructor_init);
|
||||
RUN_TEST(es_usage_crossfile_typescript);
|
||||
RUN_TEST(es_usage_crossfile_go);
|
||||
|
||||
|
||||
@@ -1837,16 +1837,19 @@ TEST(lrp_tsx_usage_import_type_with_decoy) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Python: constructor syntax is an exact call to the materialized Class node. */
|
||||
/* Python: ClassName() with a user __init__ is an exact CALLS edge to that
|
||||
* method. The Class node no longer receives the instantiation edge. */
|
||||
TEST(lrp_python_usage_instantiation) {
|
||||
static const LRP_File f[] = {
|
||||
{"model.py", "class User:\n def __init__(self, name):\n self.name = name\n"},
|
||||
{"main.py", "from .model import User\n\n\ndef create(name):\n return User(name)\n"}};
|
||||
LRP_Proj lp;
|
||||
cbm_store_t *store = lrp_index(&lp, f, 2);
|
||||
int exact = store ? lrp_exact_calls_by_name(store, lp.project, "create", "User") : -1;
|
||||
int exact = store ? lrp_exact_calls_by_name(store, lp.project, "create", "__init__") : -1;
|
||||
int class_edge = store ? lrp_exact_calls_by_name(store, lp.project, "create", "User") : -1;
|
||||
lrp_cleanup(&lp, store);
|
||||
ASSERT_EQ(exact, 1);
|
||||
ASSERT_EQ(class_edge, 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
|
||||
@@ -356,6 +356,164 @@ TEST(pylsp_constructor_call_returns_instance) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Issue #1642: Embedder(...) must resolve to Embedder.__init__, and the
|
||||
* textual callee leaf "Embedder" must still join that row. */
|
||||
TEST(pylsp_constructor_call_targets_init) {
|
||||
const char *source = "class Embedder:\n"
|
||||
" def __init__(self, model=\"x\"):\n"
|
||||
" self.model = model\n"
|
||||
"def build():\n"
|
||||
" return Embedder(model=\"y\")\n";
|
||||
CBMFileResult *r = extract_py(source);
|
||||
ASSERT_NOT_NULL(r);
|
||||
int idx = require_resolved(r, "build", "Embedder.__init__");
|
||||
ASSERT_GTE(idx, 0);
|
||||
if (idx >= 0) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[idx];
|
||||
ASSERT_STR_EQ(rc->strategy, "lsp_constructor_init");
|
||||
ASSERT_STR_EQ(rc->reason, "Embedder");
|
||||
ASSERT(strncmp(rc->callee_qn, "builtins.", 9) != 0);
|
||||
}
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
||||
if (rc->kind != CBM_RESOLVED_INVOCATION || !rc->caller_qn || !rc->callee_qn ||
|
||||
!strstr(rc->caller_qn, "build"))
|
||||
continue;
|
||||
/* One site, one target: the class node no longer receives the call. */
|
||||
ASSERT(strstr(rc->callee_qn, ".__init__") != NULL);
|
||||
ASSERT(strncmp(rc->callee_qn, "builtins.", 9) != 0);
|
||||
}
|
||||
|
||||
const CBMCall *carrier = NULL;
|
||||
for (int i = 0; i < r->calls.count; i++) {
|
||||
const CBMCall *call = &r->calls.items[i];
|
||||
if (call->callee_name && strcmp(call->callee_name, "Embedder") == 0 &&
|
||||
call->enclosing_func_qn && strstr(call->enclosing_func_qn, "build")) {
|
||||
carrier = call;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_NOT_NULL(carrier);
|
||||
const CBMResolvedCall *joined =
|
||||
cbm_pipeline_find_lsp_resolution(&r->resolved_calls, carrier, false);
|
||||
ASSERT_NOT_NULL(joined);
|
||||
ASSERT(strstr(joined->callee_qn, "Embedder.__init__") != NULL);
|
||||
ASSERT_STR_EQ(joined->strategy, "lsp_constructor_init");
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* class B(A) with only A.__init__ resolves construction of B to A.__init__. */
|
||||
TEST(pylsp_constructor_inherited_init) {
|
||||
CBMFileResult *r = extract_py("class A:\n"
|
||||
" def __init__(self):\n"
|
||||
" self.ready = True\n"
|
||||
"class B(A):\n"
|
||||
" def go(self):\n"
|
||||
" return 1\n"
|
||||
"def build():\n"
|
||||
" return B()\n");
|
||||
ASSERT_NOT_NULL(r);
|
||||
int idx = require_resolved(r, "build", "A.__init__");
|
||||
ASSERT_GTE(idx, 0);
|
||||
if (idx >= 0) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[idx];
|
||||
ASSERT_STR_EQ(rc->strategy, "lsp_constructor_init");
|
||||
ASSERT(strstr(rc->callee_qn, "B.__init__") == NULL);
|
||||
ASSERT(strncmp(rc->callee_qn, "builtins.", 9) != 0);
|
||||
}
|
||||
ASSERT_EQ(find_resolved(r, "build", "B.__init__"), -1);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* No user __init__ (and no inherited one): the call stays on the class. */
|
||||
TEST(pylsp_constructor_without_init_stays_on_class) {
|
||||
CBMFileResult *r = extract_py("class Bare:\n"
|
||||
" def method(self):\n"
|
||||
" return 1\n"
|
||||
"def build():\n"
|
||||
" return Bare()\n");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_EQ(find_resolved(r, "build", "__init__"), -1);
|
||||
int idx = -1;
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
||||
if (rc->kind == CBM_RESOLVED_INVOCATION && rc->caller_qn &&
|
||||
strstr(rc->caller_qn, "build") && rc->callee_qn && strstr(rc->callee_qn, "Bare") &&
|
||||
!strstr(rc->callee_qn, "__init__")) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_GTE(idx, 0);
|
||||
if (idx >= 0)
|
||||
ASSERT_STR_EQ(r->resolved_calls.items[idx].strategy, "lsp_constructor");
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* f = Foo(); f() is a callable instance, not a constructor. It must not
|
||||
* gain an edge to Foo.__init__. Foo() itself still does. */
|
||||
TEST(pylsp_callable_instance_call_skips_init) {
|
||||
CBMFileResult *r = extract_py("class Foo:\n"
|
||||
" def __init__(self):\n"
|
||||
" pass\n"
|
||||
" def __call__(self):\n"
|
||||
" return 1\n"
|
||||
"def use():\n"
|
||||
" f = Foo()\n"
|
||||
" return f()\n");
|
||||
ASSERT_NOT_NULL(r);
|
||||
int init_rows = 0;
|
||||
int class_rows = 0;
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
||||
if (rc->kind != CBM_RESOLVED_INVOCATION || !rc->caller_qn || !rc->callee_qn ||
|
||||
!strstr(rc->caller_qn, "use"))
|
||||
continue;
|
||||
if (strstr(rc->callee_qn, "__init__")) {
|
||||
init_rows++;
|
||||
ASSERT_STR_EQ(rc->strategy, "lsp_constructor_init");
|
||||
ASSERT_STR_EQ(rc->reason, "Foo");
|
||||
ASSERT(strncmp(rc->callee_qn, "builtins.", 9) != 0);
|
||||
} else if (strstr(rc->callee_qn, "Foo")) {
|
||||
class_rows++;
|
||||
ASSERT_STR_EQ(rc->strategy, "lsp_constructor");
|
||||
ASSERT_NULL(rc->reason);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(init_rows, 1);
|
||||
ASSERT_EQ(class_rows, 1);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* builtins.object.__init__ is in the stdlib registry. Construction must not
|
||||
* record a resolution to it. */
|
||||
TEST(pylsp_constructor_skips_builtin_init) {
|
||||
CBMFileResult *r = extract_py("from builtins import object\n"
|
||||
"def build():\n"
|
||||
" return object()\n");
|
||||
ASSERT_NOT_NULL(r);
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
||||
if (!rc->callee_qn)
|
||||
continue;
|
||||
ASSERT(strncmp(rc->callee_qn, "builtins.", 9) != 0 ||
|
||||
strstr(rc->callee_qn, ".__init__") == NULL);
|
||||
}
|
||||
int idx = find_resolved(r, "build", "builtins.object");
|
||||
ASSERT_GTE(idx, 0);
|
||||
if (idx >= 0) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[idx];
|
||||
ASSERT_STR_EQ(rc->strategy, "lsp_constructor");
|
||||
ASSERT(strstr(rc->callee_qn, "__init__") == NULL);
|
||||
}
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pylsp_method_via_inheritance) {
|
||||
CBMFileResult *r = extract_py(
|
||||
"class Base:\n"
|
||||
@@ -924,6 +1082,103 @@ TEST(pylsp_crossfile_inheritance) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Imported ClassName() resolves to that class's __init__. Bare names are
|
||||
* module-qualified during per-file registration, so the imported class has
|
||||
* to come from the cross-file registry. */
|
||||
TEST(pylsp_crossfile_constructor_targets_init) {
|
||||
const char *source = "from models import Embedder\n"
|
||||
"def build():\n"
|
||||
" return Embedder(model=\"y\")\n";
|
||||
|
||||
CBMLSPDef defs[2];
|
||||
memset(defs, 0, sizeof(defs));
|
||||
defs[0].qualified_name = "models.Embedder";
|
||||
defs[0].short_name = "Embedder";
|
||||
defs[0].label = "Class";
|
||||
defs[0].def_module_qn = "models";
|
||||
defs[0].lang = CBM_LANG_PYTHON;
|
||||
defs[1].qualified_name = "models.Embedder.__init__";
|
||||
defs[1].short_name = "__init__";
|
||||
defs[1].label = "Method";
|
||||
defs[1].receiver_type = "models.Embedder";
|
||||
defs[1].def_module_qn = "models";
|
||||
defs[1].lang = CBM_LANG_PYTHON;
|
||||
|
||||
const char *imp_names[] = {"Embedder"};
|
||||
const char *imp_qns[] = {"models.Embedder"};
|
||||
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
CBMTypeRegistry *reg = cbm_py_build_cross_registry(&arena, defs, 2);
|
||||
ASSERT_NOT_NULL(reg);
|
||||
CBMResolvedCallArray out = {0};
|
||||
cbm_run_py_lsp_cross_with_registry(&arena, source, (int)strlen(source), "test.main", reg,
|
||||
imp_names, imp_qns, 1, NULL, &out, NULL);
|
||||
|
||||
int idx = find_resolved_arr(&out, "build", "models.Embedder.__init__");
|
||||
ASSERT_GTE(idx, 0);
|
||||
if (idx >= 0) {
|
||||
ASSERT_STR_EQ(out.items[idx].strategy, "lsp_constructor_init");
|
||||
ASSERT_STR_EQ(out.items[idx].reason, "Embedder");
|
||||
ASSERT(strncmp(out.items[idx].callee_qn, "builtins.", 9) != 0);
|
||||
}
|
||||
cbm_arena_destroy(&arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* class B(A) where A's __init__ lives in another module. The cross registry
|
||||
* records the real base QN; per-file registration would have stored
|
||||
* test.main.A and missed the base. */
|
||||
TEST(pylsp_crossfile_inherited_init) {
|
||||
const char *source = "from base_mod import A\n"
|
||||
"class B(A):\n"
|
||||
" def go(self):\n"
|
||||
" return 1\n"
|
||||
"def build():\n"
|
||||
" return B()\n";
|
||||
|
||||
CBMLSPDef defs[3];
|
||||
memset(defs, 0, sizeof(defs));
|
||||
defs[0].qualified_name = "base_mod.A";
|
||||
defs[0].short_name = "A";
|
||||
defs[0].label = "Class";
|
||||
defs[0].def_module_qn = "base_mod";
|
||||
defs[0].lang = CBM_LANG_PYTHON;
|
||||
defs[1].qualified_name = "base_mod.A.__init__";
|
||||
defs[1].short_name = "__init__";
|
||||
defs[1].label = "Method";
|
||||
defs[1].receiver_type = "base_mod.A";
|
||||
defs[1].def_module_qn = "base_mod";
|
||||
defs[1].lang = CBM_LANG_PYTHON;
|
||||
defs[2].qualified_name = "test.main.B";
|
||||
defs[2].short_name = "B";
|
||||
defs[2].label = "Class";
|
||||
defs[2].def_module_qn = "test.main";
|
||||
defs[2].embedded_types = "base_mod.A";
|
||||
defs[2].lang = CBM_LANG_PYTHON;
|
||||
|
||||
const char *imp_names[] = {"A"};
|
||||
const char *imp_qns[] = {"base_mod.A"};
|
||||
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
CBMTypeRegistry *reg = cbm_py_build_cross_registry(&arena, defs, 3);
|
||||
ASSERT_NOT_NULL(reg);
|
||||
CBMResolvedCallArray out = {0};
|
||||
cbm_run_py_lsp_cross_with_registry(&arena, source, (int)strlen(source), "test.main", reg,
|
||||
imp_names, imp_qns, 1, NULL, &out, NULL);
|
||||
|
||||
int idx = find_resolved_arr(&out, "build", "base_mod.A.__init__");
|
||||
ASSERT_GTE(idx, 0);
|
||||
if (idx >= 0) {
|
||||
ASSERT_STR_EQ(out.items[idx].strategy, "lsp_constructor_init");
|
||||
ASSERT_STR_EQ(out.items[idx].reason, "B");
|
||||
ASSERT(strstr(out.items[idx].callee_qn, "B.__init__") == NULL);
|
||||
}
|
||||
cbm_arena_destroy(&arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pylsp_batch_two_files) {
|
||||
const char *src_a =
|
||||
"def helper():\n"
|
||||
@@ -5010,6 +5265,11 @@ SUITE(py_lsp) {
|
||||
RUN_TEST(pylsp_method_call_simple);
|
||||
RUN_TEST(pylsp_method_via_self);
|
||||
RUN_TEST(pylsp_constructor_call_returns_instance);
|
||||
RUN_TEST(pylsp_constructor_call_targets_init);
|
||||
RUN_TEST(pylsp_constructor_inherited_init);
|
||||
RUN_TEST(pylsp_constructor_without_init_stays_on_class);
|
||||
RUN_TEST(pylsp_callable_instance_call_skips_init);
|
||||
RUN_TEST(pylsp_constructor_skips_builtin_init);
|
||||
RUN_TEST(pylsp_method_via_inheritance);
|
||||
RUN_TEST(pylsp_no_false_positive_on_unknown_method);
|
||||
/* Phases 7-8 — decorators, super(), multi-inheritance */
|
||||
@@ -5028,6 +5288,8 @@ SUITE(py_lsp) {
|
||||
RUN_TEST(pylsp_cross_registry_interleaved_order);
|
||||
RUN_TEST(pylsp_crossfile_classmethod_on_class_issue228);
|
||||
RUN_TEST(pylsp_crossfile_inheritance);
|
||||
RUN_TEST(pylsp_crossfile_constructor_targets_init);
|
||||
RUN_TEST(pylsp_crossfile_inherited_init);
|
||||
RUN_TEST(pylsp_batch_two_files);
|
||||
RUN_TEST(pylsp_from_import_alias_equal_module_leaf_targets_imported_member);
|
||||
RUN_TEST(pylsp_project_prefixed_direct_alias_same_tail_is_not_from_import_reference);
|
||||
|
||||
Reference in new issue
Block a user