Skip to content

Commit ab0dce1

Browse files
committed
fix(swift): retain qualified parameter and return identities
Signed-off-by: DavidHLP <lysf15520112973@163.com>
1 parent e3cd15a commit ab0dce1

5 files changed

Lines changed: 104 additions & 16 deletions

File tree

‎docs/SWIFT_IDENTITY_VALIDATION.md‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,33 @@
22

33
Scope: issue #2061 / PR #2436, Swift callable identity and conservative CALLS
44
candidates only. Bare stored names remain unchanged. Generic declarations,
5-
constraints, where requirements and function-level async distinguish identities;
5+
constraints, where requirements, qualified parameter paths, explicit return types
6+
and function-level async distinguish identities;
67
they do not provide Swift compiler type inference. Whitespace/comments normalize;
78
generic parameter renaming, reordered constraints and inline/where semantic
89
equivalence are not canonicalized. Existing signature/argument limits remain
910
fail-closed. Callable identity modes for other languages remain disabled.
1011

11-
Format 3 rebuilds format 1/2 indexes once because generic/async identities change
12+
Swift keeps qualified types as `A/Item` (rather than collapsing to `Item`) and
13+
spells explicit returns as `pick()=>Int` or `pick()async=>String`. This records
14+
declaration syntax; it does not resolve aliases, infer implicit returns or claim
15+
to distinguish every legal overload. Absent returns keep the existing suffix.
16+
17+
Format 4 rebuilds format 1/2/3 indexes once because these identities change
1218
persisted QNs and old CALLS edges cannot safely be updated in place. The Swift
1319
migration regression fabricates collided nodes and an incompatible edge, verifies
1420
source positions and candidate counts after rebuilding, then no repeat migration.
1521
The TS/HTML/SCSS rebuild regression remains.
1622

17-
## Current repair evidence
23+
## P2 repair evidence
24+
25+
Review checkpoint: `500707c5b853da75a0a4fc7f1ae3b9364c04792e`. The previous
26+
generic/where/async, negative-target, migration and documentation findings remain
27+
resolved. New qualified-type and return-only collisions require independent
28+
RED/GREEN and final-source verification; the historical passes below do not
29+
establish acceptance of this repair.
30+
31+
## Previous repair evidence (historical)
1832

1933
Review checkpoint: `39d5db48a83f94641fccbb44c8cbc0233b5e2a51`, upstream base
2034
`268a9d8886642eb7f9b2ce45f5ce27cdecf0f519`. Regression-only RED checkpoint:

‎internal/cbm/callable_sig.c‎

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ enum {
2424
SIG_OPERATOR_LEN = 8, /* strlen("operator") */
2525
SIG_CONST_LEN = 5, /* strlen("const") */
2626
SIG_ASYNC_LEN = 5, /* strlen("async") */
27+
SIG_ARROW_LEN = 2, /* strlen("=>") */
2728
SIG_VOLATILE_LEN = 8, /* strlen("volatile") */
2829
SIG_ARITY_DIGITS = 16, /* "(%d)" scratch */
2930
SIG_CAPPED_TAIL = 1 + SIG_HASH_HEX + 1, /* "#" hex ")" */
@@ -933,10 +934,11 @@ static void sig_generic_params(sig_ctx_t *c, TSNode node) {
933934

934935
/* ── Spelling pass ─────────────────────────────────────────────── */
935936

936-
/* Qualified type paths keep their last segment; "->" becomes "=>"; any other
937+
/* Swift qualified paths use '/'; other type paths keep their last segment.
938+
* "->" becomes "=>"; any other
937939
* '.' or "::" (a receiver function type, a leading global scope) is dropped,
938940
* so the suffix contract holds whatever the grammar produced. */
939-
static size_t sig_spell(char *s, size_t n) {
941+
static size_t sig_spell(char *s, size_t n, CBMLanguage lang) {
940942
size_t out = 0;
941943
size_t i = 0;
942944
while (i < n) {
@@ -947,6 +949,11 @@ static size_t sig_spell(char *s, size_t n) {
947949
sep = 2;
948950
}
949951
if (sep) {
952+
if (lang == CBM_LANG_SWIFT) {
953+
s[out++] = '/';
954+
i += sep;
955+
continue;
956+
}
950957
bool ident_before = out > 0 && sig_ident_char((unsigned char)s[out - 1]);
951958
bool ident_after = i + sep < n && sig_ident_char((unsigned char)s[i + sep]);
952959
if (ident_before && ident_after) {
@@ -1079,6 +1086,11 @@ static void sig_render(sig_ctx_t *c, TSNode node) {
10791086
break;
10801087
}
10811088
}
1089+
TSNode result = sig_field(node, "return_type");
1090+
if (!ts_node_is_null(result) && sig_writes_types(c)) {
1091+
sig_raw_str(c, "=>");
1092+
sig_type_tokens(c, result);
1093+
}
10821094
}
10831095
}
10841096

@@ -1129,7 +1141,7 @@ const char *cbm_callable_sig_mode(CBMArena *a, TSNode func_node, const char *sou
11291141
if (!spelled) {
11301142
return NULL;
11311143
}
1132-
size_t n = sig_spell(spelled, full);
1144+
size_t n = sig_spell(spelled, full, lang);
11331145
if (n <= CBM_CALLABLE_SIG_MAX) {
11341146
return sig_roundtrips(spelled, n) ? spelled : sig_hashed(a, spelled, n);
11351147
}
@@ -1140,12 +1152,12 @@ const char *cbm_callable_sig_mode(CBMArena *a, TSNode func_node, const char *sou
11401152
int limit = c.too_many ? SIG_MAX_ENTRIES : c.entries;
11411153
for (int i = 0; i < limit; i++) {
11421154
char *prefix = cbm_arena_strndup(a, c.buf, c.entry_end[i]);
1143-
c.entry_end[i] = prefix ? sig_spell(prefix, c.entry_end[i]) : 0;
1155+
c.entry_end[i] = prefix ? sig_spell(prefix, c.entry_end[i], lang) : 0;
11441156
}
11451157
char *open_prefix = cbm_arena_strndup(a, c.buf, c.open_off);
11461158
char *close_prefix = cbm_arena_strndup(a, c.buf, c.close_off);
1147-
c.open_off = open_prefix ? sig_spell(open_prefix, c.open_off) : 0;
1148-
c.close_off = close_prefix ? sig_spell(close_prefix, c.close_off) : 0;
1159+
c.open_off = open_prefix ? sig_spell(open_prefix, c.open_off, lang) : 0;
1160+
c.close_off = close_prefix ? sig_spell(close_prefix, c.close_off, lang) : 0;
11491161
c.buf = spelled;
11501162
c.len = n;
11511163
const char *capped = sig_capped(a, &c, spelled, n);
@@ -1213,13 +1225,70 @@ size_t cbm_qn_callable_base_len_named(const char *qn, const char *name) {
12131225
return anchored ? base : len;
12141226
}
12151227

1228+
/* Locate only a declaration's return marker, never a nested closure arrow or
1229+
* an arrow in generic requirements. Shared by inverse and Swift registry. */
1230+
static bool sig_return_type_valid(const char *type) {
1231+
char stack[SIG_DEPTH_LIMIT];
1232+
unsigned depth = 0;
1233+
if (!type[0] || strpbrk(type, ".#;{}") || strstr(type, "::") || strstr(type, "->")) {
1234+
return false;
1235+
}
1236+
for (size_t i = 0; type[i]; i++) {
1237+
char ch = type[i];
1238+
if (strchr("([<", ch)) {
1239+
if (depth == SIG_DEPTH_LIMIT) {
1240+
return false;
1241+
}
1242+
stack[depth++] = ch;
1243+
} else if (strchr(")]>", ch) && !(ch == '>' && i > 0 && type[i - 1] == '=')) {
1244+
char open = ch == ')' ? '(' : (ch == ']' ? '[' : '<');
1245+
if (!depth || stack[--depth] != open) {
1246+
return false;
1247+
}
1248+
}
1249+
}
1250+
return depth == 0;
1251+
}
1252+
1253+
size_t cbm_callable_return_offset(const char *suffix) {
1254+
size_t len = strlen(suffix);
1255+
if (!strstr(suffix, "=>")) {
1256+
return len;
1257+
}
1258+
int parens = 0, brackets = 0, angles = 0;
1259+
for (size_t i = 0; i + 1 < len; i++) {
1260+
char ch = suffix[i];
1261+
bool arrow = ch == '=' && suffix[i + 1] == '>';
1262+
bool after_params = i && (suffix[i - 1] == ')' ||
1263+
(i >= SIG_ASYNC_LEN &&
1264+
memcmp(suffix + i - SIG_ASYNC_LEN, "async", SIG_ASYNC_LEN) == 0));
1265+
if (arrow && after_params && parens == 0 && brackets == 0 && angles == 0) {
1266+
return sig_return_type_valid(suffix + i + SIG_ARROW_LEN) ? i : 0;
1267+
}
1268+
if (ch == '(') {
1269+
parens++;
1270+
} else if (ch == ')') {
1271+
parens--;
1272+
} else if (ch == '[') {
1273+
brackets++;
1274+
} else if (ch == ']') {
1275+
brackets--;
1276+
} else if (ch == '<' && (angles > 0 || sig_ident_char((unsigned char)suffix[i + 1]))) {
1277+
angles++;
1278+
} else if (ch == '>' && angles > 0 && (i == 0 || suffix[i - 1] != '=')) {
1279+
angles--;
1280+
}
1281+
}
1282+
return len;
1283+
}
1284+
12161285
size_t cbm_qn_callable_base_len(const char *qn) {
12171286
if (!qn) {
12181287
return 0;
12191288
}
12201289
size_t len = strlen(qn);
12211290
/* Trailing cvref (C++): const / volatile / & / && after the ')'. */
1222-
size_t t = len;
1291+
size_t t = cbm_callable_return_offset(qn);
12231292
if (t >= SIG_ASYNC_LEN && memcmp(qn + t - SIG_ASYNC_LEN, "async", SIG_ASYNC_LEN) == 0) {
12241293
t -= SIG_ASYNC_LEN;
12251294
}

‎internal/cbm/callable_sig.h‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
*
44
* A callable's graph identity is
55
*
6-
* callable_qn := base_qn [<tparams>] (params) [cvref]
6+
* callable_qn := base_qn [<tparams>] (params) [cvref/async] [=>return_type]
77
* Swift includes generic declarations and where requirements in <tparams>
88
* (associated-type paths use '/' instead of '.') and appends function-level
9-
* async after params. These distinguish declarations, not call applicability.
9+
* async after params and an explicit return type after "=>". Swift preserves
10+
* qualified type paths using '/'. These distinguish declarations, not call
11+
* applicability.
1012
*
1113
* so overloads that share a base QN (Java/C#/C++/Kotlin/Swift/Scala/ObjC)
1214
* become distinct nodes instead of merging into one node that collects every
@@ -25,8 +27,8 @@
2527
* - it is whitespace-normalized: one space only between two identifier
2628
* characters ("unsigned long", "const T&"), none elsewhere;
2729
* - type spelling: comments, annotations and attributes are dropped;
28-
* qualified type paths keep their last segment (java.util.List -> List,
29-
* std::vector -> vector); "->" in function types is spelled "=>"; a
30+
* except Swift, qualified paths keep their last segment (java.util.List
31+
* -> List, std::vector -> vector); "->" in function types is spelled "=>"; a
3032
* variadic/rest marker is "[]" for Java (the JVM array it is) and "~"
3133
* everywhere else; C++ top-level cv-qualifiers are dropped and array
3234
* parameters decay to pointers, as the language itself does;
@@ -80,6 +82,9 @@ const char *cbm_callable_sig_mode(CBMArena *a, TSNode func_node, const char *sou
8082
* name, not a suffix. NULL -> 0. */
8183
size_t cbm_qn_callable_base_len(const char *qn);
8284

85+
/* Top-level declaration return marker; strlen(suffix) if absent, 0 if malformed. */
86+
size_t cbm_callable_return_offset(const char *suffix);
87+
8388
/* The inverse anchored to the callable's bare `name` (the node's name
8489
* column): the suffix is stripped only when the base QN is `name` or ends in
8590
* "." / "::" + `name`. Other QN text can end in ')' as well — a Java field

‎src/pipeline/registry.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1644,7 +1644,7 @@ static bool swift_parameters_read(const char *qn, const char *name, const swift_
16441644
swift_parameters_t *params) {
16451645
size_t base_len = cbm_qn_callable_base_len_named(qn, name);
16461646
const char *sig = qn + base_len;
1647-
size_t n = strlen(sig);
1647+
size_t n = cbm_callable_return_offset(sig);
16481648
const size_t async_len = sizeof("async") - SKIP_ONE;
16491649
if (n >= async_len && memcmp(sig + n - async_len, "async", async_len) == 0) {
16501650
n -= async_len;

‎src/store/store.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ typedef struct cbm_store cbm_store_t;
2323
#define CBM_STORE_OK 0
2424
#define CBM_STORE_ERR (-1)
2525
#define CBM_STORE_NOT_FOUND (-2)
26-
#define CBM_INDEX_FORMAT_VERSION 3
26+
#define CBM_INDEX_FORMAT_VERSION 4
2727
#define CBM_STORE_CANCELLED (-3)
2828
#define CBM_STORE_SCAN_LIMIT (-4)
2929
#define CBM_STORE_CALLBACK_ERR (-5)

0 commit comments

Comments
 (0)