perf(resolver): stop walking a Swift type's bases once the answer is open

A name that is not a type's own member asks each of its bases, so a type with many conforming extensions cost bases x names: 16,000 `extension A: P` took 157 s in resolve. Past the chain's bound of 128, every further base answers open, so the loop goes on only to reach the answer it already has.

It now stops there, once the bound has passed and the answer is already open. Nothing past that point can change the result: a base can no longer be found or be ambiguous, and an open answer returns the same whatever `declared` or `result` hold. The `extension_only` scan also stops at its first declared record.

complexity_swift_type_bases_are_near_linear: 3.84 on the old walk, 2.00 now.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-03 01:27:42 +01:00
1 parent eccd026782
commit 6802d8e8b6
2 files changed
+18 -2

No files matched your search

+7 -2
View File
@@ -41,6 +41,7 @@
#include "source_adapter_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include "stats.h"
#include "syntax_index.h"
#define SWIFT_TYPE_RECORD "&type"
@@ -1221,8 +1222,8 @@ static swift_lookup_t swift_find_member(swift_ctx_t *ctx, const char *type_key,
}
/* Only extensions here: what the extension spells is declared elsewhere, or outside. */
bool extension_only = true;
for (const cbm_source_decl_t *record = records; record; record = record->next)
extension_only &= (record->flags & CBM_SOURCE_DECL_F_ADAPTED) != 0;
for (const cbm_source_decl_t *record = records; record && extension_only; record = record->next)
extension_only = (record->flags & CBM_SOURCE_DECL_F_ADAPTED) != 0;
bool declared = false;
if (extension_only && !bases_only) {
const char *hash = strchr(type_key, '#');
@@ -1255,8 +1256,12 @@ static swift_lookup_t swift_find_member(swift_ctx_t *ctx, const char *type_key,
/* An extension of a type outside the project has no declaration here. */
const char *base_owner = type_key;
for (const cbm_source_decl_t *record = records; record; record = record->next) {
cbm_resolve_stats_add(CBM_RESOLVE_STAT_LOOKUP_PROBES, 1);
declared |= !(record->flags & CBM_SOURCE_DECL_F_ADAPTED);
open |= (record->flags & CBM_SOURCE_DECL_F_OPEN) != 0;
/* Past the chain's bound every further base answers open, and the answer is open now. */
if (open && chain->overflow)
break;
for (const char *const *base = record->bases; base && *base; base++) {
const char *parent = swift_lookup_type(ctx, base_owner, *base);
if (!parent) {
+11
View File
@@ -3263,6 +3263,16 @@ TEST(complexity_source_same_named_definitions_are_near_linear) {
PASS();
}
/* `extension A: P` many times over: a name found in none of A's bases asks each of them. */
CX_LINE3(cx_line_swift_extension_base,
"protocol P%d {}\nextension A: P%d {\n func m_%d() { base() }\n}\n")
TEST(complexity_swift_type_bases_are_near_linear) {
ASSERT_TRUE(cx_source_shape_is_near_linear(CBM_LANG_SWIFT, "bases.swift",
"class A { func base() {} }\n",
cx_line_swift_extension_base, ""));
PASS();
}
/* A barrel library re-exports the project, and every library that imports it read its whole
* export list again. */
static uint64_t cx_dart_barrel_work(int libraries) {
@@ -3457,6 +3467,7 @@ SUITE(complexity) {
RUN_TEST(complexity_syntax_index_prev_siblings_answer_as_tree_sitter_does);
RUN_TEST(complexity_prev_sibling_walk_is_linear);
RUN_TEST(complexity_source_same_named_definitions_are_near_linear);
RUN_TEST(complexity_swift_type_bases_are_near_linear);
RUN_TEST(complexity_dart_barrel_exports_are_near_linear);
RUN_TEST(complexity_go_route_bindings_are_near_linear);
RUN_TEST(complexity_flat_preceding_label_is_linear);