mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
feat(resolver): recover Java, Kotlin and C# edges reference assemblies and lost spellings had voided
dotnet/runtime CALLS go from 392,440 to 773,575 and USAGE from 709,951 to 1,688,346; elasticsearch CALLS from 1,308,131 to 1,342,695 and Exposed from 20,357 to 23,603. The same type in ref/ and src/ now means the implementation stands for the contract. A reader means its own file's declaration or the only one in its build project, and .projitems counts as a project. A same-length parse view reads spellings the grammar loses: Vector128.cs goes from 0 to 172 methods. Lambdas take their parameters from the callee's single abstract method. A CALLS edge can now start from a Field node, and calls in C# conversion operators, indexers and property bodies belong to those members. Five wrong edge kinds found while sampling are fixed. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
c1eb542277
commit
0c4a757972
20 files changed
+4675
-232
No files matched your search
+345
-1
@@ -1099,11 +1099,349 @@ static bool csharp_line_starts(const char *line, size_t room, const char *word)
|
||||
return room >= length && !strncmp(line, word, length);
|
||||
}
|
||||
|
||||
/* True when what ends just before `at` lets a unary operator start an expression there:
|
||||
* punctuation, or `return`, `ref` and `out`. An identifier or `)` before a star is multiplication.
|
||||
*/
|
||||
static bool csharp_expression_may_start(const char *view, uint32_t at) {
|
||||
static const char *const WORDS[] = {"return", "ref", "out", NULL};
|
||||
uint32_t p = at;
|
||||
while (p > 0 && isspace((unsigned char)view[p - 1]))
|
||||
p--;
|
||||
if (p == 0)
|
||||
return true;
|
||||
if (strchr("=(,>{;:?&|!+-*/%<[", view[p - 1]))
|
||||
return true;
|
||||
for (const char *const *word = WORDS; *word; word++) {
|
||||
uint32_t size = (uint32_t)strlen(*word);
|
||||
if (p >= size && !strncmp(view + p - size, *word, size) &&
|
||||
(p == size ||
|
||||
!(isalnum((unsigned char)view[p - size - 1]) || view[p - size - 1] == '_')))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool csharp_word_char(char c) {
|
||||
return isalnum((unsigned char)c) || c == '_';
|
||||
}
|
||||
|
||||
/* `=` or a compound assignment at `at`, after blanks. */
|
||||
static bool csharp_assignment_follows(const char *view, uint32_t length, uint32_t at) {
|
||||
while (at < length && (view[at] == ' ' || view[at] == '\t'))
|
||||
at++;
|
||||
if (at + 2 >= length)
|
||||
return false;
|
||||
if (view[at] == '=')
|
||||
return view[at + 1] != '=' && view[at + 1] != '>';
|
||||
if (view[at] && strchr("+-*/%&|^", view[at]) && view[at + 1] == '=')
|
||||
return true;
|
||||
return view[at + 2] == '=' && (!strncmp(view + at, "??", 2) || !strncmp(view + at, "<<", 2) ||
|
||||
!strncmp(view + at, ">>", 2));
|
||||
}
|
||||
|
||||
/* Blanks `from` up to `to`, keeping the lines. */
|
||||
static void csharp_view_blank(char *view, uint32_t from, uint32_t to) {
|
||||
for (uint32_t i = from; i < to; i++)
|
||||
if (view[i] != '\n' && view[i] != '\r')
|
||||
view[i] = ' ';
|
||||
}
|
||||
|
||||
/* `*(T*)p`, `*(p + 1)` and `*++p`: the grammar loses a whole file to a star in front of a
|
||||
* parenthesis or an increment. Blank the star. `(T*)p` then reads as a cast to T, which is what the
|
||||
* dereference yields. As an assignment's target no such form reads, and a write through a pointer
|
||||
* names no member of its own, so the whole target becomes `_`. The scan stops at the statement's
|
||||
* end, so the pass stays linear. */
|
||||
static void csharp_view_blank_dereference(char *view, uint32_t length) {
|
||||
for (uint32_t i = 0; i + 2 < length; i++) {
|
||||
if (view[i] != '*')
|
||||
continue;
|
||||
bool may_start = csharp_expression_may_start(view, i);
|
||||
if (may_start && ((view[i + 1] == '+' && view[i + 2] == '+') ||
|
||||
(view[i + 1] == '-' && view[i + 2] == '-'))) {
|
||||
view[i] = ' ';
|
||||
continue;
|
||||
}
|
||||
if (view[i + 1] != '(')
|
||||
continue;
|
||||
uint32_t j = i + 2;
|
||||
int depth = 1;
|
||||
while (j < length && depth > 0 && view[j] != ';' && view[j] != '{' && view[j] != '}') {
|
||||
depth += view[j] == '(';
|
||||
depth -= view[j] == ')';
|
||||
j++;
|
||||
}
|
||||
if (depth != 0)
|
||||
continue;
|
||||
uint32_t end = j < length && view[j] == '&' ? j + 1 : j;
|
||||
int operand_depth = 0;
|
||||
for (; end < length && view[end] != ';' && view[end] != '\n'; end++) {
|
||||
char c = view[end];
|
||||
operand_depth += c == '(';
|
||||
operand_depth -= c == ')';
|
||||
if (operand_depth < 0 ||
|
||||
(operand_depth == 0 && !csharp_word_char(c) && c != '.' && c != ')'))
|
||||
break;
|
||||
}
|
||||
if (csharp_assignment_follows(view, length, end)) {
|
||||
csharp_view_blank(view, i, end);
|
||||
view[i] = '_';
|
||||
continue;
|
||||
}
|
||||
if (!may_start)
|
||||
continue;
|
||||
view[i] = ' ';
|
||||
for (uint32_t k = j - 2; k > i && view[k] == '*'; k--)
|
||||
view[k] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
/* `a?.b = c`, `a?[i] = c` (C# 14): the grammar has no null-conditional assignment, and what it
|
||||
* loses can take the members after the statement with it. `a .b = c` assigns the same member. Each
|
||||
* chain is read once, so the pass stays linear. */
|
||||
static void csharp_view_blank_conditional_target(char *view, uint32_t length) {
|
||||
for (uint32_t i = 0; i + 1 < length; i++) {
|
||||
if (view[i] != '?' || (view[i + 1] != '.' && view[i + 1] != '['))
|
||||
continue;
|
||||
uint32_t end = i + 1;
|
||||
int depth = 0;
|
||||
for (; end < length && view[end] != '\n' && view[end] != ';'; end++) {
|
||||
char c = view[end];
|
||||
if (c == '[' || c == '(') {
|
||||
depth++;
|
||||
} else if (c == ']' || c == ')') {
|
||||
if (depth == 0)
|
||||
break;
|
||||
depth--;
|
||||
} else if (depth == 0 && !csharp_word_char(c) && c != '.' && c != '?') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (depth == 0 && csharp_assignment_follows(view, length, end)) {
|
||||
for (uint32_t k = i; k < end; k++) {
|
||||
depth += view[k] == '[' || view[k] == '(';
|
||||
depth -= view[k] == ']' || view[k] == ')';
|
||||
if (depth == 0 && view[k] == '?' && (view[k + 1] == '.' || view[k + 1] == '['))
|
||||
view[k] = ' ';
|
||||
}
|
||||
}
|
||||
i = end;
|
||||
}
|
||||
}
|
||||
|
||||
/* The brace closing the one at `open`, or `length`. Comments, strings and characters hold none. */
|
||||
static uint32_t csharp_matching_brace(const char *view, uint32_t length, uint32_t open) {
|
||||
int depth = 0;
|
||||
for (uint32_t i = open; i < length; i++) {
|
||||
char c = view[i];
|
||||
if (c == '/' && view[i + 1] == '/') {
|
||||
const char *newline = memchr(view + i, '\n', length - i);
|
||||
if (!newline)
|
||||
return length;
|
||||
i = (uint32_t)(newline - view);
|
||||
} else if (c == '/' && view[i + 1] == '*') {
|
||||
const char *close = strstr(view + i + 2, "*/");
|
||||
if (!close)
|
||||
return length;
|
||||
i = (uint32_t)(close - view) + 1;
|
||||
} else if (c == '"') {
|
||||
bool verbatim = i > 0 && (view[i - 1] == '@' ||
|
||||
(view[i - 1] == '$' && i > 1 && view[i - 2] == '@'));
|
||||
if (!strncmp(view + i, "\"\"\"", 3)) {
|
||||
const char *close = strstr(view + i + 3, "\"\"\"");
|
||||
if (!close)
|
||||
return length;
|
||||
i = (uint32_t)(close - view) + 2;
|
||||
continue;
|
||||
}
|
||||
for (i++; i < length; i++) {
|
||||
if (!verbatim && view[i] == '\\')
|
||||
i++;
|
||||
else if (verbatim && view[i] == '"' && view[i + 1] == '"')
|
||||
i++;
|
||||
else if (view[i] == '"' || (!verbatim && view[i] == '\n'))
|
||||
break;
|
||||
}
|
||||
} else if (c == '\'') {
|
||||
if (view[i + 1] == '\\') {
|
||||
const char *close = memchr(view + i + 2, '\'', length - i - 2);
|
||||
if (!close)
|
||||
return length;
|
||||
i = (uint32_t)(close - view);
|
||||
} else if (view[i + 1] && view[i + 2] == '\'') {
|
||||
i += 2;
|
||||
}
|
||||
} else if (c == '{') {
|
||||
depth++;
|
||||
} else if (c == '}' && --depth == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/* An extension block (C# 14) the grammar cannot read: `extension<T>(Vector128<T>) where ... { ...
|
||||
* }`. It knows a block with a named, non-generic receiver as a constructor, so a named receiver
|
||||
* only loses what is in the way. A block with no receiver name has none to lose: its braces go, and
|
||||
* its members, which are the enclosing static class's in the assembly too, stand as that class's.
|
||||
* Every search resumes past what it read, so the pass stays linear. */
|
||||
static void csharp_view_open_extension_blocks(char *view, uint32_t length) {
|
||||
static const char WORD[] = "extension";
|
||||
enum { WORD_LENGTH = sizeof(WORD) - 1 };
|
||||
for (char *hit = view; (hit = strstr(hit, WORD)) != NULL; hit += WORD_LENGTH) {
|
||||
uint32_t start = (uint32_t)(hit - view);
|
||||
uint32_t p = start;
|
||||
while (p > 0 && (view[p - 1] == ' ' || view[p - 1] == '\t'))
|
||||
p--;
|
||||
if (p > 0 && view[p - 1] != '\n')
|
||||
continue;
|
||||
uint32_t i = start + WORD_LENGTH;
|
||||
uint32_t generic = i;
|
||||
if (i < length && view[i] == '<') {
|
||||
int angle = 0;
|
||||
for (; i < length && view[i] != '\n'; i++) {
|
||||
angle += view[i] == '<';
|
||||
angle -= view[i] == '>';
|
||||
if (angle == 0)
|
||||
break;
|
||||
}
|
||||
if (i >= length || view[i] != '>')
|
||||
continue;
|
||||
i++;
|
||||
}
|
||||
uint32_t generic_end = i;
|
||||
if (i >= length || view[i] != '(')
|
||||
continue;
|
||||
uint32_t open = i;
|
||||
int depth = 0;
|
||||
for (; i < length && view[i] != '\n'; i++) {
|
||||
depth += view[i] == '(';
|
||||
depth -= view[i] == ')';
|
||||
if (depth == 0)
|
||||
break;
|
||||
}
|
||||
if (i >= length || view[i] != ')' || i == open + 1)
|
||||
continue;
|
||||
uint32_t close = i;
|
||||
for (i = close + 1; i < length && !strchr("{};=", view[i]); i++) {}
|
||||
if (i >= length || view[i] != '{')
|
||||
continue;
|
||||
/* `(Stream stream)` names its receiver: a word with something in front of it. */
|
||||
uint32_t word = close;
|
||||
while (word > open + 1 && csharp_word_char(view[word - 1]))
|
||||
word--;
|
||||
uint32_t before = word;
|
||||
while (before > open + 1 && view[before - 1] == ' ')
|
||||
before--;
|
||||
bool named = word < close && before > open + 1;
|
||||
if (named) {
|
||||
csharp_view_blank(view, generic, generic_end);
|
||||
csharp_view_blank(view, close + 1, i);
|
||||
continue;
|
||||
}
|
||||
uint32_t end = csharp_matching_brace(view, length, i);
|
||||
if (end >= length)
|
||||
continue;
|
||||
csharp_view_blank(view, start, i + 1);
|
||||
view[end] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
static bool csharp_word_in(const char *word, size_t size, const char *const *set) {
|
||||
for (; *set; set++)
|
||||
if (strlen(*set) == size && !strncmp(word, *set, size))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* `safe` (as in `safe extern`) and `closed` (on a class) are modifiers the grammar does not have:
|
||||
* it took `safe` for the return type and `extern` for the method's name. Modifiers say nothing a
|
||||
* name needs. Each is a modifier only between words that show it: another modifier before, a word
|
||||
* after. */
|
||||
static void csharp_view_blank_new_modifiers(char *view, uint32_t length) {
|
||||
static const char *const BEFORE[] = {"public", "private", "protected", "internal", "static",
|
||||
"extern", "unsafe", "readonly", "partial", "new",
|
||||
"override", "sealed", "abstract", "virtual", NULL};
|
||||
static const char *const CLASS_WORDS[] = {"class", "record", "partial", NULL};
|
||||
static const struct {
|
||||
const char *word;
|
||||
const char *const *after; /* NULL: any word. */
|
||||
} MODIFIERS[] = {{"safe", NULL}, {"closed", CLASS_WORDS}};
|
||||
for (size_t m = 0; m < sizeof(MODIFIERS) / sizeof(MODIFIERS[0]); m++) {
|
||||
size_t size = strlen(MODIFIERS[m].word);
|
||||
for (char *hit = view; (hit = strstr(hit, MODIFIERS[m].word)) != NULL; hit += size) {
|
||||
if ((hit > view && csharp_word_char(hit[-1])) || csharp_word_char(hit[size]))
|
||||
continue;
|
||||
char *after = hit + size;
|
||||
while (*after == ' ')
|
||||
after++;
|
||||
char *after_end = after;
|
||||
while (csharp_word_char(*after_end))
|
||||
after_end++;
|
||||
if (after_end == after ||
|
||||
(MODIFIERS[m].after &&
|
||||
!csharp_word_in(after, (size_t)(after_end - after), MODIFIERS[m].after)))
|
||||
continue;
|
||||
char *before_end = hit;
|
||||
while (before_end > view && before_end[-1] == ' ')
|
||||
before_end--;
|
||||
char *before = before_end;
|
||||
while (before > view && csharp_word_char(before[-1]))
|
||||
before--;
|
||||
if (csharp_word_in(before, (size_t)(before_end - before), BEFORE))
|
||||
memset(hit, ' ', size);
|
||||
}
|
||||
}
|
||||
(void)length;
|
||||
}
|
||||
|
||||
/* `bool async`, `if (async)`, `async: true`: an identifier the grammar takes for the modifier. Only
|
||||
* the parse sees this spelling: every name is read back from the source at the same span. */
|
||||
static void csharp_view_rename_async_identifier(char *view, uint32_t length) {
|
||||
static const char WORD[] = "async";
|
||||
enum { WORD_LENGTH = sizeof(WORD) - 1 };
|
||||
for (char *at = view; (at = strstr(at, WORD)) != NULL; at += WORD_LENGTH) {
|
||||
bool starts = at == view || !(isalnum((unsigned char)at[-1]) || at[-1] == '_');
|
||||
char *next = at + WORD_LENGTH;
|
||||
if (!starts || isalnum((unsigned char)*next) || *next == '_')
|
||||
continue;
|
||||
while (next < view + length && (*next == ' ' || *next == '\t'))
|
||||
next++;
|
||||
if (*next && strchr("),:;?&|.]", *next))
|
||||
at[WORD_LENGTH - 1] = '_';
|
||||
else if (next[0] == '=' && next[1] != '>')
|
||||
at[WORD_LENGTH - 1] = '_';
|
||||
}
|
||||
}
|
||||
|
||||
/* `$@"""{x}"""`: a doubled quote in a verbatim interpolated string is one quote of its text. */
|
||||
static void csharp_view_blank_doubled_quotes(char *view, uint32_t length) {
|
||||
for (uint32_t i = 0; i + 2 < length; i++) {
|
||||
bool opens = (view[i] == '$' && view[i + 1] == '@' && view[i + 2] == '"') ||
|
||||
(view[i] == '@' && view[i + 1] == '$' && view[i + 2] == '"');
|
||||
if (!opens)
|
||||
continue;
|
||||
uint32_t j = i + 3;
|
||||
while (j < length) {
|
||||
if (view[j] != '"') {
|
||||
j++;
|
||||
} else if (j + 1 < length && view[j + 1] == '"') {
|
||||
view[j] = view[j + 1] = ' ';
|
||||
j += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
/* C#: the grammar reads `#if` only where a whole statement or member can stand, and
|
||||
* does not know `ref partial struct`. Either can turn a whole file into one ERROR,
|
||||
* taking every type it declares with it. This view has the same length and the same
|
||||
* lines: conditional directives and the branches after a file's first one are blank,
|
||||
* and so is that `ref`. Only a file the grammar could not otherwise read uses it. */
|
||||
* and so is that `ref`. So are the three spellings repaired below, seen to break whole files
|
||||
* on dotnet/runtime (Task.cs, EventSource.cs, Vector2.cs, every test with a `bool async`).
|
||||
* Only a file the grammar could not otherwise read uses it. */
|
||||
static char *csharp_parse_view(const char *source, uint32_t length, bool *hid_branch) {
|
||||
enum { MAX_DEPTH = 64 };
|
||||
char *view = cbm_alloc(CBM_MEM_CLASS_EXTRACT, (size_t)length + 1);
|
||||
@@ -1157,6 +1495,12 @@ static char *csharp_parse_view(const char *source, uint32_t length, bool *hid_br
|
||||
if (!strncmp(next, "struct", 6))
|
||||
memset(ref, ' ', 3);
|
||||
}
|
||||
csharp_view_blank_dereference(view, length);
|
||||
csharp_view_rename_async_identifier(view, length);
|
||||
csharp_view_blank_doubled_quotes(view, length);
|
||||
csharp_view_blank_conditional_target(view, length);
|
||||
csharp_view_open_extension_blocks(view, length);
|
||||
csharp_view_blank_new_modifiers(view, length);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -871,6 +871,9 @@ TSNode cbm_resolve_func_name(TSNode node, CBMLanguage lang) {
|
||||
if (lang == CBM_LANG_CSHARP && strcmp(kind, "operator_declaration") == 0) {
|
||||
return ts_node_child_by_field_name(node, TS_FIELD("operator"));
|
||||
}
|
||||
if (lang == CBM_LANG_CSHARP && !ts_node_is_null(cbm_csharp_keyword_name(node))) {
|
||||
return cbm_csharp_keyword_name(node);
|
||||
}
|
||||
|
||||
if (lang == CBM_LANG_HASKELL && strcmp(kind, "signature") == 0) {
|
||||
TSNode null_node = {0};
|
||||
@@ -9823,7 +9826,8 @@ static TSNode resolve_method_name(TSNode child, CBMLanguage lang) {
|
||||
if (((lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA ||
|
||||
lang == CBM_LANG_GLSL) &&
|
||||
strcmp(ck, "function_definition") == 0) ||
|
||||
(lang == CBM_LANG_CSHARP && strcmp(ck, "operator_declaration") == 0)) {
|
||||
(lang == CBM_LANG_CSHARP && (strcmp(ck, "operator_declaration") == 0 ||
|
||||
!ts_node_is_null(cbm_csharp_keyword_name(child))))) {
|
||||
return cbm_resolve_func_name(child, lang);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,24 @@ static const char *extract_type_text(CBMArena *a, TSNode node, const char *sourc
|
||||
return clean_type_name(a, cbm_node_text(a, node, source));
|
||||
}
|
||||
|
||||
/* Java and C# spell a wrapper around the name: `ActionListener<Void>`, `Foo[]`, `Foo?`. The name
|
||||
* is the leftmost leaf, so each level costs one child lookup. The wrapper itself when no leaf
|
||||
* spells it exactly, as with an annotated type. */
|
||||
static TSNode jvm_clr_exact_type_site(const CBMExtractCtx *ctx, TSNode site, const char *name) {
|
||||
enum { MAX_WRAPPERS = 8 };
|
||||
size_t length = strlen(name);
|
||||
TSNode at = site;
|
||||
for (int depth = 0; depth < MAX_WRAPPERS && !ts_node_is_null(at); depth++) {
|
||||
uint32_t start = ts_node_start_byte(at), end = ts_node_end_byte(at);
|
||||
if (end - start == length && end <= (uint32_t)ctx->source_len &&
|
||||
memcmp(ctx->source + start, name, length) == 0) {
|
||||
return at;
|
||||
}
|
||||
at = ts_node_named_child_count(at) ? ts_node_named_child(at, 0) : (TSNode){0};
|
||||
}
|
||||
return site;
|
||||
}
|
||||
|
||||
// Add a type reference for a function.
|
||||
static void add_type_ref(CBMExtractCtx *ctx, const char *type_name, const char *func_qn,
|
||||
TSNode site) {
|
||||
@@ -115,6 +133,9 @@ static void add_type_ref(CBMExtractCtx *ctx, const char *type_name, const char *
|
||||
if (is_builtin_type(type_name)) {
|
||||
return;
|
||||
}
|
||||
if (ctx->language == CBM_LANG_JAVA || ctx->language == CBM_LANG_CSHARP) {
|
||||
site = jvm_clr_exact_type_site(ctx, site, type_name);
|
||||
}
|
||||
|
||||
CBMTypeRef tr = {0};
|
||||
tr.type_name = type_name;
|
||||
@@ -231,8 +252,11 @@ static void extract_param_type_refs(CBMExtractCtx *ctx, TSNode params, const cha
|
||||
|
||||
// Extract return type references.
|
||||
static void extract_return_type_refs(CBMExtractCtx *ctx, TSNode func_node, const char *func_qn) {
|
||||
const char *fields[] = {"result", "return_type", "type", NULL};
|
||||
const char *fields[] = {"result", "return_type", "type", "returns", NULL};
|
||||
for (const char **f = fields; *f; f++) {
|
||||
if (strcmp(*f, "returns") == 0 && ctx->language != CBM_LANG_CSHARP) {
|
||||
continue;
|
||||
}
|
||||
TSNode rt = ts_node_child_by_field_name(func_node, *f, (uint32_t)strlen(*f));
|
||||
if (!ts_node_is_null(rt) && ts_family(ctx->language)) {
|
||||
add_ts_annotation_refs(ctx, rt, func_qn, 0);
|
||||
|
||||
@@ -970,6 +970,48 @@ static bool jvm_anonymous_member(CBMLanguage language, TSNode function) {
|
||||
return strcmp(ts_node_type(ts_node_named_child(call, 0)), "object_literal") == 0;
|
||||
}
|
||||
|
||||
/* A local function has no node. Two C# shapes only look like one: a method of an extension block
|
||||
* the grammar took for a constructor has a node of its own (#2071), and so may a member after a
|
||||
* statement the grammar lost, which is why it can carry an access modifier no local function can.
|
||||
*/
|
||||
static bool local_function_without_node(CBMExtractCtx *ctx, TSNode node, const WalkState *state) {
|
||||
const char *outer = state->enclosing_func_qn;
|
||||
if (!outer) {
|
||||
return false;
|
||||
}
|
||||
const char *kind = ts_node_type(node);
|
||||
if (ctx->language == CBM_LANG_KOTLIN) {
|
||||
TSNode parent = ts_node_parent(node);
|
||||
return strcmp(kind, "function_declaration") == 0 && !ts_node_is_null(parent) &&
|
||||
strcmp(ts_node_type(parent), "statements") == 0;
|
||||
}
|
||||
if (ctx->language != CBM_LANG_CSHARP || strcmp(kind, "local_function_statement") != 0) {
|
||||
return false;
|
||||
}
|
||||
static const char EXTENSION[] = ".extension";
|
||||
size_t outer_size = strlen(outer);
|
||||
size_t suffix_size = sizeof(EXTENSION) - 1;
|
||||
if (outer_size >= suffix_size && strcmp(outer + outer_size - suffix_size, EXTENSION) == 0) {
|
||||
return false;
|
||||
}
|
||||
bool member = false;
|
||||
TSTreeCursor cursor = ts_tree_cursor_new(node);
|
||||
if (ts_tree_cursor_goto_first_child(&cursor)) {
|
||||
do {
|
||||
TSNode child = ts_tree_cursor_current_node(&cursor);
|
||||
if (strcmp(ts_node_type(child), "modifier") != 0) {
|
||||
continue;
|
||||
}
|
||||
const char *word = ctx->source + ts_node_start_byte(child);
|
||||
member = member || strncmp(word, "public", 6) == 0 ||
|
||||
strncmp(word, "private", 7) == 0 || strncmp(word, "protected", 9) == 0 ||
|
||||
strncmp(word, "internal", 8) == 0;
|
||||
} while (ts_tree_cursor_goto_next_sibling(&cursor));
|
||||
}
|
||||
ts_tree_cursor_delete(&cursor);
|
||||
return !member;
|
||||
}
|
||||
|
||||
static const char *compute_func_qn(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
|
||||
WalkState *state) {
|
||||
/* Raw configured definitions already own the canonical QN, including
|
||||
@@ -982,7 +1024,9 @@ static const char *compute_func_qn(CBMExtractCtx *ctx, TSNode node, const CBMLan
|
||||
/* A member of an anonymous class has no node of its own. Like a lambda's body,
|
||||
* its body belongs to the enclosing declaration; a QN of its own named a source
|
||||
* no node carries, and every call in it was dropped. */
|
||||
if (jvm_anonymous_member(ctx->language, node) && state->enclosing_func_qn) {
|
||||
if ((jvm_anonymous_member(ctx->language, node) ||
|
||||
local_function_without_node(ctx, node, state)) &&
|
||||
state->enclosing_func_qn) {
|
||||
return state->enclosing_func_qn;
|
||||
}
|
||||
/* Nor has a Rust fn nested in a function body, or a method of an impl written there, and
|
||||
@@ -2799,6 +2843,19 @@ static void push_boundary_scopes(CBMExtractCtx *ctx, TSNode node, const CBMLangS
|
||||
state->os_type_map.class_base_count = 0;
|
||||
}
|
||||
}
|
||||
} else if (ctx->language == CBM_LANG_CSHARP &&
|
||||
strcmp(ts_node_type(node), "property_declaration") == 0 &&
|
||||
state->enclosing_class_qn) {
|
||||
/* A property has its node. What its accessors, its expression body and its initializer
|
||||
* call belongs to it, not to the file. */
|
||||
TSNode name = ts_node_child_by_field_name(node, TS_FIELD("name"));
|
||||
if (!ts_node_is_null(name)) {
|
||||
(void)push_function_scope(
|
||||
state, depth,
|
||||
cbm_arena_sprintf(ctx->arena, "%s.%s", state->enclosing_class_qn,
|
||||
cbm_node_text(ctx->arena, name, ctx->source)),
|
||||
node);
|
||||
}
|
||||
} else if (ctx->language == CBM_LANG_NIX && strcmp(ts_node_type(node), "binding") == 0 &&
|
||||
cbm_nix_binding_is_attrset_scope(node)) {
|
||||
/* Nix: a binding whose value is an attribute set is a named scope, exactly
|
||||
|
||||
+25
-4
@@ -584,6 +584,19 @@ TSNode cbm_find_child_by_kind(TSNode parent, const char *kind) {
|
||||
return null_node;
|
||||
}
|
||||
|
||||
TSNode cbm_csharp_keyword_name(TSNode node) {
|
||||
const char *kind = ts_node_type(node);
|
||||
if (strcmp(kind, "indexer_declaration") == 0) {
|
||||
return cbm_find_child_by_kind(node, "this");
|
||||
}
|
||||
if (strcmp(kind, "conversion_operator_declaration") == 0) {
|
||||
TSNode word = cbm_find_child_by_kind(node, "implicit");
|
||||
return ts_node_is_null(word) ? cbm_find_child_by_kind(node, "explicit") : word;
|
||||
}
|
||||
TSNode null_node = {0};
|
||||
return null_node;
|
||||
}
|
||||
|
||||
int cbm_find_children_by_kind(TSNode parent, const char *kind, TSNode *out, int max) {
|
||||
int n = 0;
|
||||
uint32_t count = ts_node_child_count(parent);
|
||||
@@ -984,8 +997,9 @@ static const char *func_kinds_ocaml[] = {"value_definition", "let_binding", NULL
|
||||
static const char *func_kinds_zig[] = {"function_declaration", "test_declaration", NULL};
|
||||
static const char *func_kinds_bash[] = {"function_definition", NULL};
|
||||
static const char *func_kinds_erlang[] = {"function_clause", NULL};
|
||||
static const char *func_kinds_csharp[] = {"method_declaration", "constructor_declaration",
|
||||
"operator_declaration", NULL};
|
||||
static const char *func_kinds_csharp[] = {"method_declaration", "constructor_declaration",
|
||||
"operator_declaration", "conversion_operator_declaration",
|
||||
"indexer_declaration", NULL};
|
||||
static const char *func_kinds_matlab[] = {"function_definition", NULL};
|
||||
static const char *func_kinds_lean[] = {"def", "theorem", "instance", "abbrev", NULL};
|
||||
static const char *func_kinds_form[] = {"procedure_definition", NULL};
|
||||
@@ -1327,8 +1341,9 @@ char *cbm_func_name_node_text(CBMArena *a, TSNode name_node, const char *source,
|
||||
/* C# `operator +` is named by its token alone, an anonymous node; any other C# name
|
||||
* is a named node. Spelled like C++'s `operator+` and `operator bool`, here so the
|
||||
* def name and the call-scope QN cannot disagree. */
|
||||
if (text && lang == CBM_LANG_CSHARP && !ts_node_is_named(name_node)) {
|
||||
bool word = text[0] >= 'a' && text[0] <= 'z'; /* operator true, operator false */
|
||||
if (text && lang == CBM_LANG_CSHARP && !ts_node_is_named(name_node) &&
|
||||
strcmp(text, "this") != 0) {
|
||||
bool word = text[0] >= 'a' && text[0] <= 'z'; /* operator true, implicit */
|
||||
return cbm_arena_sprintf(a, "operator%s%s", word ? " " : "", text);
|
||||
}
|
||||
/* Nix quoted attrpath segment: `"kebab-case" = a: a;` and `services."my.svc" = …`
|
||||
@@ -1626,6 +1641,12 @@ static const char *func_node_name(CBMArena *a, TSNode func_node, const char *sou
|
||||
TSNode op = ts_node_child_by_field_name(func_node, TS_FIELD("operator"));
|
||||
return ts_node_is_null(op) ? NULL : cbm_func_name_node_text(a, op, source, lang);
|
||||
}
|
||||
if (lang == CBM_LANG_CSHARP) {
|
||||
TSNode word = cbm_csharp_keyword_name(func_node);
|
||||
if (!ts_node_is_null(word)) {
|
||||
return cbm_func_name_node_text(a, word, source, lang);
|
||||
}
|
||||
}
|
||||
// Arrow functions: check parent variable_declarator
|
||||
if (strcmp(ts_node_type(func_node), "arrow_function") == 0) {
|
||||
TSNode parent = ts_node_parent(func_node);
|
||||
|
||||
@@ -220,6 +220,10 @@ char *cbm_go_receiver_type_name(CBMArena *a, TSNode recv, const char *source);
|
||||
// Find a child node by kind string.
|
||||
TSNode cbm_find_child_by_kind(TSNode parent, const char *kind);
|
||||
|
||||
// C#: a conversion operator has no name, its `implicit` or `explicit` names it. An indexer's
|
||||
// `this` does. Null for anything else.
|
||||
TSNode cbm_csharp_keyword_name(TSNode node);
|
||||
|
||||
// Find every child node matching `kind` (unlike cbm_find_child_by_kind,
|
||||
// which stops at the first match). Writes up to `max` nodes into `out`;
|
||||
// returns how many were found. Repeated sibling nodes of the same kind are
|
||||
|
||||
@@ -369,10 +369,17 @@ static const char *cpp_assign_types[] = {"assignment_expression", "update_expres
|
||||
static const char *cpp_throw_types[] = {"throw_statement", NULL};
|
||||
|
||||
// ==================== C# ====================
|
||||
static const char *cs_func_types[] = {
|
||||
"destructor_declaration", "local_function_statement", "function_pointer_type",
|
||||
"constructor_declaration", "anonymous_method_expression", "lambda_expression",
|
||||
"method_declaration", "operator_declaration", NULL};
|
||||
static const char *cs_func_types[] = {"destructor_declaration",
|
||||
"local_function_statement",
|
||||
"function_pointer_type",
|
||||
"constructor_declaration",
|
||||
"anonymous_method_expression",
|
||||
"lambda_expression",
|
||||
"method_declaration",
|
||||
"operator_declaration",
|
||||
"conversion_operator_declaration",
|
||||
"indexer_declaration",
|
||||
NULL};
|
||||
static const char *cs_class_types[] = {"class_declaration",
|
||||
"struct_declaration",
|
||||
"enum_declaration",
|
||||
|
||||
+841
-63
File diff suppressed because it is too large.
Load diff
+679
-60
File diff suppressed because it is too large.
Load diff
@@ -25,7 +25,11 @@ enum {
|
||||
OO_F_COMPANION = 1u << 11,
|
||||
OO_F_INCOMPLETE = 1u << 12, /* Syntax errors hid part of this declaration. */
|
||||
OO_F_PROPERTY_GET = 1u << 13,
|
||||
OO_F_INNER = 1u << 14
|
||||
OO_F_INNER = 1u << 14,
|
||||
OO_F_DELEGATE = 1u << 15, /* C#: its Invoke is the type's one callable shape. */
|
||||
OO_F_CONVERTS = 1u << 16, /* C#: declares a user-defined implicit conversion. */
|
||||
OO_F_VALUE = 1u << 18, /* C#: a struct. Its object members come through ValueType. */
|
||||
OO_F_DATA = 1u << 17 /* Kotlin data class: its primary constructor gives componentN. */
|
||||
};
|
||||
|
||||
typedef struct oo_typeref {
|
||||
@@ -35,7 +39,9 @@ typedef struct oo_typeref {
|
||||
bool function; /* Kotlin function type: args are its parameters. */
|
||||
const struct oo_typeref *fn_receiver;
|
||||
const struct oo_typeref *fn_return;
|
||||
bool by_ref; /* C# `ref` or `out` parameter. */
|
||||
bool by_ref; /* C# `ref` or `out` parameter. */
|
||||
bool ref_optional; /* C# `in` or `ref readonly`: takes `ref` and a plain argument. */
|
||||
const struct oo_typeref *lower; /* Java `? super X`: reads as nothing, takes an X. */
|
||||
} oo_typeref_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -56,6 +62,9 @@ typedef struct oo_file {
|
||||
oo_import_t *imports;
|
||||
int import_count;
|
||||
bool has_error;
|
||||
bool reference; /* C#: a reference assembly's source. */
|
||||
bool test_source; /* Its project's tests: nothing outside that project reads them. */
|
||||
const char *project; /* The build project this file compiles in, or NULL. */
|
||||
} oo_file_t;
|
||||
|
||||
typedef struct oo_type oo_type_t;
|
||||
@@ -70,8 +79,9 @@ typedef struct {
|
||||
|
||||
struct oo_type {
|
||||
const char *qn, *name;
|
||||
const char *graph_qn; /* NULL when no single declaration node represents it. */
|
||||
const char *graph_qn; /* A partial type's node for the whole type; see oo_index_type. */
|
||||
int graph_count;
|
||||
const char *graph_rel; /* The file of graph_qn. */
|
||||
const oo_file_t *file;
|
||||
const char *ns;
|
||||
const oo_type_t *owner;
|
||||
@@ -84,6 +94,13 @@ struct oo_type {
|
||||
const oo_typeref_t *alias_of;
|
||||
const oo_type_t *companion; /* Kotlin companion object. */
|
||||
oo_type_t *next_in_index;
|
||||
const struct oo_callable *abstract_method; /* The last one declared here, for a lambda. */
|
||||
int abstract_count;
|
||||
/* A name several declarations claim. The index holds one OO_F_AMBIGUOUS entry for the name;
|
||||
* `variants` lists the declarations, each with its own members. A variant's qn is unique. */
|
||||
const oo_type_t *variants;
|
||||
const oo_type_t *next_variant;
|
||||
const struct oo_callable *primary; /* Kotlin primary constructor. */
|
||||
};
|
||||
|
||||
typedef struct oo_exprsum oo_exprsum_t;
|
||||
@@ -96,6 +113,7 @@ typedef struct oo_callable {
|
||||
int min_args, max_args; /* max_args -1: variadic. */
|
||||
int vararg_position; /* Kotlin: 1 + the vararg's index; it need not be last. */
|
||||
const oo_typeref_t **params;
|
||||
const char **param_names; /* Kotlin: for a named argument. */
|
||||
int param_count;
|
||||
const oo_typeref_t *ret;
|
||||
const oo_exprsum_t *ret_expr; /* Kotlin expression body without an annotation. */
|
||||
@@ -147,6 +165,11 @@ struct cbm_oo_index {
|
||||
CBMHashTable *ext_static; /* C#: ns \x1f name -> extension methods */
|
||||
oo_type_t *all_types;
|
||||
bool sealed, failed;
|
||||
bool reference_files; /* C#: the files being added are reference assemblies. */
|
||||
const char *project; /* The build project of the files being added. */
|
||||
CBMHashTable *files; /* rel -> oo_file_t, for a file with a project. */
|
||||
bool has_variants;
|
||||
int variant_serial; /* Keys each later declaration of a several-claimed name. */
|
||||
};
|
||||
|
||||
/* Members an anonymous object declares itself: they have no index entry and
|
||||
@@ -154,6 +177,7 @@ struct cbm_oo_index {
|
||||
typedef struct {
|
||||
const char **names;
|
||||
int count;
|
||||
struct oo_value *values; /* A property's type as its declaration gave it; unknown otherwise. */
|
||||
} oo_hidden_t;
|
||||
|
||||
typedef struct oo_value {
|
||||
@@ -163,14 +187,17 @@ typedef struct oo_value {
|
||||
int arg_count;
|
||||
int dims;
|
||||
bool classref; /* Denotes the type itself rather than an instance. */
|
||||
bool null_literal; /* C#: `null`, which reaches any reference parameter. */
|
||||
bool function_literal; /* An argument spelled as a lambda: its own type is not read. */
|
||||
bool small_literal; /* An integer literal: it fits a narrower or wider parameter. */
|
||||
bool by_ref; /* C#: passed with `ref` or `out`. */
|
||||
int lambda_parameters; /* A function literal's parameter count plus one; 0 when not known. */
|
||||
const oo_hidden_t *hidden;
|
||||
/* Kotlin function value. */
|
||||
const struct oo_value *fn_receiver;
|
||||
const struct oo_value *fn_return;
|
||||
const oo_typeref_t *fn_ref;
|
||||
const struct oo_value *lower; /* `? super X`: unknown as a value, X as a lambda parameter. */
|
||||
} oo_value_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -238,8 +265,11 @@ typedef struct oo_receiver {
|
||||
oo_value_t value;
|
||||
bool unknown; /* A receiver exists whose member set cannot be listed. */
|
||||
bool anonymous; /* Own members have no index entry. */
|
||||
bool object_super; /* A supertype of the anonymous object being walked. */
|
||||
const char **own_names; /* Anonymous body member names. */
|
||||
int own_count;
|
||||
const bool *own_function; /* Kotlin: each own name is a function, not a property. */
|
||||
const struct oo_value *own_values; /* Kotlin: what each own property holds, once walked. */
|
||||
bool static_context;
|
||||
bool extension;
|
||||
struct oo_receiver *parent;
|
||||
@@ -285,7 +315,17 @@ void oo_index_function(cbm_oo_index_t *ix, const char *ns, oo_callable_t *callab
|
||||
void oo_index_prop(cbm_oo_index_t *ix, const char *ns, oo_field_t *field);
|
||||
void oo_index_ext_static(cbm_oo_index_t *ix, const char *ns, oo_callable_t *callable);
|
||||
|
||||
/* A reference assembly declares this type's name, and an implementation has it:
|
||||
* the declaration in `file` adds nothing to the type. */
|
||||
bool oo_type_is_implemented_elsewhere(const oo_type_t *type, const oo_file_t *file);
|
||||
|
||||
const oo_type_t *oo_lookup_type(const cbm_oo_index_t *ix, const char *qn);
|
||||
/* The declaration this file is lexically inside of, when `qn` has several. */
|
||||
const oo_type_t *oo_lookup_declared(const cbm_oo_index_t *ix, const char *qn, const char *rel);
|
||||
/* The type a dotted name means from this file: one declaration of a name several claim is meant
|
||||
* when it is in the file itself, or is the only one in the file's project. Otherwise the entry
|
||||
* comes back with OO_F_AMBIGUOUS. */
|
||||
const oo_type_t *oo_lookup_visible(const cbm_oo_index_t *ix, const char *qn, const oo_file_t *file);
|
||||
const oo_callable_t *oo_lookup_methods(const cbm_oo_index_t *ix, const char *qn, const char *name);
|
||||
const oo_field_t *oo_lookup_fields(const cbm_oo_index_t *ix, const char *qn, const char *name);
|
||||
const oo_callable_t *oo_lookup_functions(const cbm_oo_index_t *ix, const char *ns,
|
||||
@@ -331,6 +371,21 @@ bool oo_names_tparam(const oo_walk_t *w, const char *name);
|
||||
oo_value_t oo_element_of(oo_walk_t *w, const oo_value_t *value);
|
||||
/* The declared result of a standard collection accessor, or unknown. */
|
||||
oo_value_t oo_external_member_return(oo_walk_t *w, const oo_value_t *value, const char *name);
|
||||
/* The parameters a lambda takes for a standard collection method on a known element type: how many,
|
||||
* or -1 when the method is not known to take one here. `position` is the lambda's argument index.
|
||||
*/
|
||||
int oo_stdlib_lambda_parameters(oo_walk_t *w, const oo_value_t *receiver, const char *name,
|
||||
int position, int argc, oo_value_t *out, int capacity);
|
||||
/* The same collection with these element types: `map` on a stream or an optional. */
|
||||
oo_value_t oo_external_with_element(oo_walk_t *w, const oo_value_t *value, oo_value_t element);
|
||||
/* `Optional.of(x)`, `List.of(a, b)`, `Arrays.stream(items)`: what a JDK factory holds is spelled by
|
||||
* its arguments. Arguments that disagree settle nothing: that is a least upper bound. */
|
||||
oo_value_t oo_jdk_factory_result(oo_walk_t *w, const oo_value_t *receiver, const char *name,
|
||||
const oo_value_t *args, int argc);
|
||||
/* A lambda's parameters when the target is a project interface with one abstract method, or a C#
|
||||
* delegate: how many, or -1 when it has no such single shape. */
|
||||
int oo_functional_parameters(oo_walk_t *w, const oo_value_t *function, oo_value_t *out,
|
||||
int capacity);
|
||||
/* Follows a type's aliases to the declared type. */
|
||||
const oo_type_t *oo_unalias(const cbm_oo_index_t *ix, const oo_type_t *type);
|
||||
|
||||
@@ -359,6 +414,15 @@ oo_select_t oo_select_from_list(oo_walk_t *w, const oo_callable_t *list, int arg
|
||||
const oo_value_t *args, const oo_value_t *receiver);
|
||||
bool oo_callable_visible(const oo_walk_t *w, const oo_callable_t *callable);
|
||||
oo_value_t oo_callable_return(oo_walk_t *w, const oo_callable_t *callable, const oo_subst_t *subst);
|
||||
/* `subst` plus the callee's own type parameters that one argument (or the receiver of an
|
||||
* extension) settles. Two arguments that disagree settle nothing: that is a least upper bound. */
|
||||
/* `ref` names one of the callee's type parameters that nothing settled: its value is only a bound,
|
||||
* which says what the type at least has, not what it lacks. */
|
||||
bool oo_ref_unsettled(const oo_callable_t *callee, const oo_typeref_t *ref,
|
||||
const oo_subst_t *subst);
|
||||
const oo_subst_t *oo_infer_call_subst(oo_walk_t *w, const oo_callable_t *callee,
|
||||
const oo_subst_t *subst, const oo_value_t *receiver,
|
||||
const oo_value_t *args, int argc);
|
||||
|
||||
/* Walk state. */
|
||||
bool oo_walk_init(oo_walk_t *w, const cbm_oo_index_t *ix, CBMArena *arena, const char *source,
|
||||
|
||||
+318
-32
@@ -113,10 +113,11 @@ static const oo_typeref_t *j_typeref(const j_text_t *t, TSNode node) {
|
||||
} else if (!strcmp(kind, "wildcard")) {
|
||||
const char *text = j_text(t, node);
|
||||
/* `? extends X` reads as X; `? super X` and `?` read as nothing. */
|
||||
if (text && strstr(text, "extends")) {
|
||||
uint32_t count = ts_node_named_child_count(node);
|
||||
return count ? j_typeref(t, ts_node_named_child(node, count - 1)) : ref;
|
||||
}
|
||||
uint32_t count = ts_node_named_child_count(node);
|
||||
if (text && count && strstr(text, "extends"))
|
||||
return j_typeref(t, ts_node_named_child(node, count - 1));
|
||||
if (text && count && strstr(text, "super"))
|
||||
ref->lower = j_typeref(t, ts_node_named_child(node, count - 1));
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
@@ -411,6 +412,12 @@ static void jh_method(jh_t *h, oo_type_t *type, TSNode node, bool is_interface)
|
||||
callable->tparams =
|
||||
j_tparams(&h->text, j_field(node, "type_parameters"), &callable->tparam_count);
|
||||
oo_index_method(h->ix, type, callable);
|
||||
/* JLS 9.8: Object's public methods do not count. */
|
||||
if (is_interface && !ctor && ts_node_is_null(j_field(node, "body")) && strcmp(name, "equals") &&
|
||||
strcmp(name, "hashCode") && strcmp(name, "toString")) {
|
||||
type->abstract_method = callable;
|
||||
type->abstract_count++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool j_type_decl(TSNode node) {
|
||||
@@ -567,11 +574,22 @@ bool oo_java_harvest(cbm_oo_index_t *ix, const char *rel, const CBMFileResult *f
|
||||
|
||||
/* ── Walk ───────────────────────────────────────────────────────────── */
|
||||
|
||||
/* One call's lambda arguments. Their parameter types come from the declaration the call selects,
|
||||
* so they are walked after selection, once each. */
|
||||
typedef struct {
|
||||
TSNode *nodes; /* By argument position; a null node is no lambda. */
|
||||
int count;
|
||||
bool walked;
|
||||
oo_value_t last; /* The last lambda's expression body. */
|
||||
} jw_lambdas_t;
|
||||
|
||||
typedef struct {
|
||||
oo_walk_t *w;
|
||||
j_text_t text;
|
||||
int depth;
|
||||
bool static_method;
|
||||
jw_lambdas_t *lambdas; /* The call whose declaration is being selected. */
|
||||
TSNode returns; /* The enclosing method's declared result. */
|
||||
} jw_t;
|
||||
|
||||
static oo_value_t jw_expr(jw_t *j, TSNode node);
|
||||
@@ -611,6 +629,7 @@ static const oo_typeref_t *jw_type_at(jw_t *j, TSNode node) {
|
||||
}
|
||||
|
||||
static void jw_emit_type_mentions(jw_t *j, TSNode node, unsigned depth);
|
||||
static void jw_annotations(jw_t *j, TSNode owner);
|
||||
|
||||
/* Every type mention in a type expression: the identifier leaves at their own
|
||||
* spans, resolved in the current scope. */
|
||||
@@ -621,8 +640,16 @@ static void jw_type_leaf(jw_t *j, TSNode node) {
|
||||
return;
|
||||
oo_typeref_t ref = {.name = text};
|
||||
oo_value_t value = jw_ref(j, &ref);
|
||||
if (value.type && value.type->graph_qn && !oo_names_tparam(w, text))
|
||||
oo_emit_type_ref(w, node, value.type->graph_qn, "java-type-name-lookup");
|
||||
if (!value.type || !value.type->graph_qn || oo_names_tparam(w, text))
|
||||
return;
|
||||
oo_emit_type_ref(w, node, value.type->graph_qn, "java-type-name-lookup");
|
||||
/* A dotted spelling's USAGE occurrence is its last segment. */
|
||||
TSNode leaf = node;
|
||||
uint32_t parts = ts_node_named_child_count(node);
|
||||
if (j_is(node, "scoped_type_identifier") && parts)
|
||||
leaf = ts_node_named_child(node, parts - 1);
|
||||
oo_emit_usage(w, leaf, value.type->graph_qn, CBM_RESOLVED_TYPE_REFERENCE,
|
||||
CBM_RESOLVE_RULE_DECLARED_TYPE, "java-type-name-lookup");
|
||||
}
|
||||
|
||||
static void jw_emit_type_mentions(jw_t *j, TSNode node, unsigned depth) {
|
||||
@@ -630,6 +657,9 @@ static void jw_emit_type_mentions(jw_t *j, TSNode node, unsigned depth) {
|
||||
return;
|
||||
if (j_is(node, "type_identifier") || j_is(node, "scoped_type_identifier")) {
|
||||
jw_type_leaf(j, node);
|
||||
/* `Outer.Inner` also mentions Outer. */
|
||||
if (j_is(node, "scoped_type_identifier") && ts_node_named_child_count(node))
|
||||
jw_emit_type_mentions(j, ts_node_named_child(node, 0), depth + 1);
|
||||
return;
|
||||
}
|
||||
uint32_t count = ts_node_named_child_count(node);
|
||||
@@ -637,6 +667,45 @@ static void jw_emit_type_mentions(jw_t *j, TSNode node, unsigned depth) {
|
||||
jw_emit_type_mentions(j, ts_node_named_child(node, i), depth + 1);
|
||||
}
|
||||
|
||||
/* `@Nullable`, `@Foo(Bar.BAZ)`: the annotation's type and its value expressions. */
|
||||
static void jw_annotation(jw_t *j, TSNode node) {
|
||||
oo_walk_t *w = j->w;
|
||||
TSNode name = j_field(node, "name");
|
||||
const char *spelled = ts_node_is_null(name) ? NULL : j_compact(&j->text, name);
|
||||
if (spelled) {
|
||||
oo_typeref_t ref = {.name = spelled};
|
||||
oo_value_t type = jw_ref(j, &ref);
|
||||
uint32_t parts = ts_node_named_child_count(name);
|
||||
TSNode leaf =
|
||||
j_is(name, "scoped_identifier") && parts ? ts_node_named_child(name, parts - 1) : name;
|
||||
if (type.type && type.type->graph_qn)
|
||||
oo_emit_usage(w, leaf, type.type->graph_qn, CBM_RESOLVED_TYPE_REFERENCE,
|
||||
CBM_RESOLVE_RULE_DECLARED_TYPE, "java-annotation-type");
|
||||
}
|
||||
TSNode arguments = j_field(node, "arguments");
|
||||
uint32_t count = ts_node_is_null(arguments) ? 0 : ts_node_named_child_count(arguments);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
TSNode argument = ts_node_named_child(arguments, i);
|
||||
/* `name = value`: the name is the annotation's element. */
|
||||
TSNode value = j_is(argument, "element_value_pair") ? j_field(argument, "value") : argument;
|
||||
if (j_is(value, "annotation") || j_is(value, "marker_annotation"))
|
||||
jw_annotation(j, value);
|
||||
else
|
||||
(void)jw_expr(j, value);
|
||||
}
|
||||
}
|
||||
|
||||
/* The annotations in a declaration's modifiers. */
|
||||
static void jw_annotations(jw_t *j, TSNode owner) {
|
||||
TSNode modifiers = j_child(owner, "modifiers");
|
||||
uint32_t count = ts_node_is_null(modifiers) ? 0 : ts_node_named_child_count(modifiers);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
TSNode child = ts_node_named_child(modifiers, i);
|
||||
if (j_is(child, "annotation") || j_is(child, "marker_annotation"))
|
||||
jw_annotation(j, child);
|
||||
}
|
||||
}
|
||||
|
||||
static void jw_bind_declared(jw_t *j, const char *name, TSNode type, oo_value_t initial, int dims) {
|
||||
oo_walk_t *w = j->w;
|
||||
oo_value_t value;
|
||||
@@ -656,6 +725,7 @@ static void jw_parameters(jw_t *j, TSNode parameters) {
|
||||
uint32_t count = ts_node_is_null(parameters) ? 0 : ts_node_named_child_count(parameters);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
TSNode parameter = ts_node_named_child(parameters, i);
|
||||
jw_annotations(j, parameter);
|
||||
if (j_is(parameter, "formal_parameter")) {
|
||||
jw_bind_declared(j, j_text(&j->text, j_field(parameter, "name")),
|
||||
j_field(parameter, "type"), oo_value_unknown(),
|
||||
@@ -690,12 +760,19 @@ static int jw_argument_count(TSNode arguments) {
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Arguments are ordinary expressions at this site: evaluate each exactly once. */
|
||||
static const oo_value_t *jw_arguments(jw_t *j, TSNode arguments, int *out_count) {
|
||||
/* Arguments are ordinary expressions at this site: evaluate each exactly once. A lambda waits for
|
||||
* selection when the caller collects it in `deferred`; either way it reaches only a function type.
|
||||
*/
|
||||
static const oo_value_t *jw_arguments(jw_t *j, TSNode arguments, int *out_count,
|
||||
jw_lambdas_t *deferred) {
|
||||
int count = jw_argument_count(arguments);
|
||||
*out_count = count;
|
||||
oo_value_t *values =
|
||||
count ? cbm_arena_calloc(j->w->arena, (size_t)count * sizeof(*values)) : NULL;
|
||||
if (deferred && count) {
|
||||
deferred->nodes = cbm_arena_calloc(j->w->arena, (size_t)count * sizeof(TSNode));
|
||||
deferred->count = deferred->nodes ? count : 0;
|
||||
}
|
||||
int at = 0;
|
||||
uint32_t children = ts_node_is_null(arguments) ? 0 : ts_node_named_child_count(arguments);
|
||||
for (uint32_t i = 0; i < children; i++) {
|
||||
@@ -703,7 +780,20 @@ static const oo_value_t *jw_arguments(jw_t *j, TSNode arguments, int *out_count)
|
||||
const char *kind = ts_node_type(child);
|
||||
if (!strcmp(kind, "line_comment") || !strcmp(kind, "block_comment"))
|
||||
continue;
|
||||
oo_value_t value = jw_expr(j, child);
|
||||
bool lambda = !strcmp(kind, "lambda_expression");
|
||||
oo_value_t value = oo_value_unknown();
|
||||
if (lambda && deferred && deferred->nodes)
|
||||
deferred->nodes[at] = child;
|
||||
else
|
||||
value = jw_expr(j, child);
|
||||
if (lambda || !strcmp(kind, "method_reference"))
|
||||
value.function_literal = true;
|
||||
if (lambda) {
|
||||
TSNode parameters = j_field(child, "parameters");
|
||||
value.lambda_parameters =
|
||||
1 +
|
||||
(j_is(parameters, "identifier") ? 1 : (int)ts_node_named_child_count(parameters));
|
||||
}
|
||||
if (values)
|
||||
values[at] = value;
|
||||
at++;
|
||||
@@ -711,6 +801,138 @@ static const oo_value_t *jw_arguments(jw_t *j, TSNode arguments, int *out_count)
|
||||
return values;
|
||||
}
|
||||
|
||||
/* JDK functional interfaces: the positions of their type arguments a lambda's parameters take.
|
||||
* A name counts only as what the declaring file imports it from. */
|
||||
typedef struct {
|
||||
const char *leaf, *package;
|
||||
int count;
|
||||
int positions[2];
|
||||
} jw_functional_t;
|
||||
|
||||
static const jw_functional_t JW_FUNCTIONAL[] = {
|
||||
{"Consumer", "java.util.function", 1, {0, 0}},
|
||||
{"BiConsumer", "java.util.function", 2, {0, 1}},
|
||||
{"Function", "java.util.function", 1, {0, 0}},
|
||||
{"BiFunction", "java.util.function", 2, {0, 1}},
|
||||
{"UnaryOperator", "java.util.function", 1, {0, 0}},
|
||||
{"BinaryOperator", "java.util.function", 2, {0, 0}},
|
||||
{"Predicate", "java.util.function", 1, {0, 0}},
|
||||
{"BiPredicate", "java.util.function", 2, {0, 1}},
|
||||
{"ToIntFunction", "java.util.function", 1, {0, 0}},
|
||||
{"ToLongFunction", "java.util.function", 1, {0, 0}},
|
||||
{"ToDoubleFunction", "java.util.function", 1, {0, 0}},
|
||||
{"ToIntBiFunction", "java.util.function", 2, {0, 1}},
|
||||
{"ToLongBiFunction", "java.util.function", 2, {0, 1}},
|
||||
{"ToDoubleBiFunction", "java.util.function", 2, {0, 1}},
|
||||
{"Comparator", "java.util", 2, {0, 0}},
|
||||
};
|
||||
|
||||
static bool jw_imports_star(const oo_file_t *file, const char *package) {
|
||||
for (int i = 0; file && i < file->import_count; i++)
|
||||
if (file->imports[i].star && !file->imports[i].statik &&
|
||||
!strcmp(file->imports[i].path, package))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int jw_functional_parameters(jw_t *j, const oo_value_t *function, const oo_file_t *file,
|
||||
oo_value_t *out, int capacity) {
|
||||
if (function->type)
|
||||
return oo_functional_parameters(j->w, function, out, capacity);
|
||||
if (!function->external || function->dims || capacity < 2)
|
||||
return -1;
|
||||
const char *dot = strrchr(function->external, '.');
|
||||
const char *leaf = dot ? dot + 1 : function->external;
|
||||
for (size_t t = 0; t < sizeof(JW_FUNCTIONAL) / sizeof(JW_FUNCTIONAL[0]); t++) {
|
||||
const jw_functional_t *known = &JW_FUNCTIONAL[t];
|
||||
if (strcmp(known->leaf, leaf))
|
||||
continue;
|
||||
size_t package = strlen(known->package);
|
||||
bool named = dot ? (size_t)(dot - function->external) == package &&
|
||||
!strncmp(function->external, known->package, package)
|
||||
: jw_imports_star(file, known->package);
|
||||
if (!named)
|
||||
return -1;
|
||||
for (int i = 0; i < known->count; i++) {
|
||||
int at = known->positions[i];
|
||||
oo_value_t value = at < function->arg_count ? function->args[at] : oo_value_unknown();
|
||||
out[i] = !oo_value_known(&value) && value.lower ? *value.lower : value;
|
||||
}
|
||||
return known->count;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A lambda under the parameter types its target gave, `count` of them, or none known at -1. */
|
||||
static oo_value_t jw_lambda_typed(jw_t *j, TSNode node, const oo_value_t *types, int count) {
|
||||
oo_walk_t *w = j->w;
|
||||
oo_frame_t frame;
|
||||
oo_frame_push(w, &frame);
|
||||
TSNode parameters = j_field(node, "parameters");
|
||||
if (j_is(parameters, "identifier")) {
|
||||
oo_bind(w, j_text(&j->text, parameters), count == 1 ? types[0] : oo_value_unknown());
|
||||
} else if (j_is(parameters, "inferred_parameters")) {
|
||||
uint32_t names = ts_node_named_child_count(parameters);
|
||||
for (uint32_t i = 0; i < names; i++)
|
||||
oo_bind(w, j_text(&j->text, ts_node_named_child(parameters, i)),
|
||||
count == (int)names ? types[i] : oo_value_unknown());
|
||||
} else {
|
||||
jw_parameters(j, parameters);
|
||||
}
|
||||
TSNode saved_returns = j->returns;
|
||||
j->returns = (TSNode){0};
|
||||
oo_value_t body = jw_expr(j, j_field(node, "body"));
|
||||
j->returns = saved_returns;
|
||||
oo_frame_pop(w);
|
||||
return body;
|
||||
}
|
||||
|
||||
enum { JW_LAMBDA_PARAMETERS = 8 };
|
||||
|
||||
/* `Consumer<Doc> c = d -> d.go();`, `return d -> d.go();`: the declared type is the target. */
|
||||
static bool jw_lambda_to_declared(jw_t *j, TSNode lambda, TSNode type) {
|
||||
if (!j_is(lambda, "lambda_expression") || ts_node_is_null(type))
|
||||
return false;
|
||||
oo_value_t declared = jw_ref(j, j_typeref(&j->text, type));
|
||||
oo_value_t types[JW_LAMBDA_PARAMETERS];
|
||||
int count = jw_functional_parameters(j, &declared, j->w->f, types, JW_LAMBDA_PARAMETERS);
|
||||
(void)jw_lambda_typed(j, lambda, types, count);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Walk a call's lambdas: under the selected declaration, under a standard collection method of
|
||||
* `receiver` called `name`, or with unknown parameters when neither applies. */
|
||||
static void jw_finish_lambdas(jw_t *j, jw_lambdas_t *lambdas, const oo_callable_t *callee,
|
||||
const oo_subst_t *subst, const oo_value_t *receiver,
|
||||
const char *name) {
|
||||
if (!lambdas || lambdas->walked)
|
||||
return;
|
||||
lambdas->walked = true;
|
||||
oo_walk_t *w = j->w;
|
||||
for (int i = 0; i < lambdas->count; i++) {
|
||||
if (ts_node_is_null(lambdas->nodes[i]))
|
||||
continue;
|
||||
oo_value_t types[JW_LAMBDA_PARAMETERS];
|
||||
int count = -1;
|
||||
if (callee && callee->params && callee->param_count) {
|
||||
int index = i < callee->param_count ? i : callee->param_count - 1;
|
||||
if (callee->params[index]) {
|
||||
oo_scope_t scope = oo_scope_of_callable(callee);
|
||||
oo_value_t function = oo_value_from_ref(w, callee->params[index], &scope, subst);
|
||||
/* `Consumer<T>... each`: the element. */
|
||||
if (callee->max_args < 0 && index == callee->param_count - 1 && function.dims)
|
||||
function.dims--;
|
||||
count = jw_functional_parameters(j, &function, callee->file, types,
|
||||
JW_LAMBDA_PARAMETERS);
|
||||
}
|
||||
} else if (receiver && name) {
|
||||
count = oo_stdlib_lambda_parameters(w, receiver, name, i, lambdas->count, types,
|
||||
JW_LAMBDA_PARAMETERS);
|
||||
}
|
||||
lambdas->last = jw_lambda_typed(j, lambdas->nodes[i], types, count);
|
||||
}
|
||||
}
|
||||
|
||||
static oo_value_t jw_select_emit(jw_t *j, TSNode site, const oo_value_t *receiver, const char *name,
|
||||
int argc, const oo_value_t *args, bool static_only,
|
||||
const char *decision) {
|
||||
@@ -721,7 +943,10 @@ static oo_value_t jw_select_emit(jw_t *j, TSNode site, const oo_value_t *receive
|
||||
if (selected.callable->graph_qn)
|
||||
oo_emit_call(w, site, selected.callable->graph_qn, CBM_RESOLVE_RULE_TYPE_MEMBER,
|
||||
CBM_RESOLVE_FACT_RECEIVER_TYPED, decision);
|
||||
return oo_callable_return(w, selected.callable, selected.subst);
|
||||
const oo_subst_t *subst =
|
||||
oo_infer_call_subst(w, selected.callable, selected.subst, NULL, args, argc);
|
||||
jw_finish_lambdas(j, j->lambdas, selected.callable, subst, NULL, NULL);
|
||||
return oo_callable_return(w, selected.callable, subst);
|
||||
}
|
||||
|
||||
static bool jw_receiver_owns(const oo_receiver_t *receiver, const char *name) {
|
||||
@@ -743,7 +968,7 @@ static oo_value_t jw_static_import_call(jw_t *j, TSNode site, const char *name,
|
||||
const char *dot = strrchr(import->path, '.');
|
||||
const char *owner_qn =
|
||||
dot ? cbm_arena_strndup(w->arena, import->path, (size_t)(dot - import->path)) : NULL;
|
||||
const oo_type_t *owner = owner_qn ? oo_lookup_type(w->ix, owner_qn) : NULL;
|
||||
const oo_type_t *owner = owner_qn ? oo_lookup_visible(w->ix, owner_qn, w->f) : NULL;
|
||||
if (!owner || (owner->flags & OO_F_AMBIGUOUS))
|
||||
return oo_value_unknown();
|
||||
oo_value_t receiver = oo_value_of_type(owner);
|
||||
@@ -757,7 +982,7 @@ static oo_value_t jw_static_import_call(jw_t *j, TSNode site, const char *name,
|
||||
const oo_import_t *import = &file->imports[i];
|
||||
if (!import->statik || !import->star)
|
||||
continue;
|
||||
const oo_type_t *owner = oo_lookup_type(w->ix, import->path);
|
||||
const oo_type_t *owner = oo_lookup_visible(w->ix, import->path, w->f);
|
||||
/* Members of an unindexed class take part in the same overload set. */
|
||||
if (!owner || (owner->flags & OO_F_AMBIGUOUS))
|
||||
return oo_value_unknown();
|
||||
@@ -871,8 +1096,8 @@ static oo_value_t jw_identifier(jw_t *j, TSNode node) {
|
||||
? import->path
|
||||
: cbm_arena_strndup(w->arena, import->path,
|
||||
strlen(import->path) - strlen(name) - 1);
|
||||
const oo_type_t *owner = owner_qn ? oo_lookup_type(w->ix, owner_qn) : NULL;
|
||||
if (!owner)
|
||||
const oo_type_t *owner = owner_qn ? oo_lookup_visible(w->ix, owner_qn, w->f) : NULL;
|
||||
if (!owner || (owner->flags & OO_F_AMBIGUOUS))
|
||||
continue;
|
||||
oo_value_t receiver = oo_value_of_type(owner);
|
||||
oo_subst_t *subst = NULL;
|
||||
@@ -926,7 +1151,7 @@ static oo_value_t jw_field_access(jw_t *j, TSNode node) {
|
||||
(oo_find_field(w, &r->value, head, &subst, &incomplete) || incomplete));
|
||||
}
|
||||
}
|
||||
const oo_type_t *exact = variable ? NULL : oo_lookup_type(w->ix, text);
|
||||
const oo_type_t *exact = variable ? NULL : oo_lookup_visible(w->ix, text, w->f);
|
||||
if (exact && !(exact->flags & OO_F_AMBIGUOUS)) {
|
||||
oo_value_t value = oo_value_of_type(exact);
|
||||
value.classref = true;
|
||||
@@ -947,13 +1172,16 @@ static oo_value_t jw_field_access(jw_t *j, TSNode node) {
|
||||
if (nested && !(nested->flags & OO_F_AMBIGUOUS)) {
|
||||
oo_value_t value = oo_value_of_type(nested);
|
||||
value.classref = true;
|
||||
if (nested->graph_qn)
|
||||
oo_emit_usage(w, field_node, nested->graph_qn, CBM_RESOLVED_TYPE_REFERENCE,
|
||||
CBM_RESOLVE_RULE_DECLARED_TYPE, "java-nested-type-name");
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return oo_value_unknown();
|
||||
}
|
||||
|
||||
static oo_value_t jw_method_invocation(jw_t *j, TSNode node) {
|
||||
static oo_value_t jw_method_invocation_with(jw_t *j, TSNode node, jw_lambdas_t *lambdas) {
|
||||
oo_walk_t *w = j->w;
|
||||
TSNode object = j_field(node, "object");
|
||||
TSNode name_node = j_field(node, "name");
|
||||
@@ -965,12 +1193,14 @@ static oo_value_t jw_method_invocation(jw_t *j, TSNode node) {
|
||||
}
|
||||
if (ts_node_is_null(object)) {
|
||||
int argc = 0;
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc);
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc, lambdas);
|
||||
j->lambdas = lambdas;
|
||||
return jw_bare_call(j, node, name, argc, args);
|
||||
}
|
||||
if (j_is(object, "super")) {
|
||||
int argc = 0;
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc);
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc, lambdas);
|
||||
j->lambdas = lambdas;
|
||||
/* super.m() selects from the direct supertypes of the enclosing class. */
|
||||
const oo_value_t self = jw_this_value(w);
|
||||
if (!self.type)
|
||||
@@ -996,12 +1226,39 @@ static oo_value_t jw_method_invocation(jw_t *j, TSNode node) {
|
||||
if (j_is(object, "field_access") && j_is(j_field(object, "field"), "super"))
|
||||
receiver.classref = false;
|
||||
int argc = 0;
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc);
|
||||
if (!receiver.type)
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc, lambdas);
|
||||
j->lambdas = lambdas;
|
||||
if (!receiver.type) {
|
||||
jw_finish_lambdas(j, lambdas, NULL, NULL, &receiver, name);
|
||||
/* `map` yields what its lambda's expression produced, in the same kind of container, and
|
||||
* `flatMap` what that holds. */
|
||||
if ((!strcmp(name, "map") || !strcmp(name, "mapToObj")) && receiver.external &&
|
||||
oo_value_known(&lambdas->last))
|
||||
return oo_external_with_element(w, &receiver, lambdas->last);
|
||||
if (!strcmp(name, "flatMap") && receiver.external) {
|
||||
oo_value_t held = oo_element_of(w, &lambdas->last);
|
||||
return oo_external_with_element(w, &receiver, held);
|
||||
}
|
||||
oo_value_t made = oo_jdk_factory_result(w, &receiver, name, args, argc);
|
||||
if (oo_value_known(&made))
|
||||
return made;
|
||||
return oo_external_member_return(w, &receiver, name);
|
||||
return jw_select_emit(j, node, &receiver, name, argc, args, receiver.classref,
|
||||
receiver.classref ? "java-static-member"
|
||||
: "java-declared-receiver-member");
|
||||
}
|
||||
oo_value_t result =
|
||||
jw_select_emit(j, node, &receiver, name, argc, args, receiver.classref,
|
||||
receiver.classref ? "java-static-member" : "java-declared-receiver-member");
|
||||
/* A project collection's `forEach` comes from the JDK supertype. */
|
||||
jw_finish_lambdas(j, lambdas, NULL, NULL, &receiver, name);
|
||||
return result;
|
||||
}
|
||||
|
||||
static oo_value_t jw_method_invocation(jw_t *j, TSNode node) {
|
||||
jw_lambdas_t lambdas = {0};
|
||||
jw_lambdas_t *outer = j->lambdas;
|
||||
oo_value_t result = jw_method_invocation_with(j, node, &lambdas);
|
||||
jw_finish_lambdas(j, &lambdas, NULL, NULL, NULL, NULL);
|
||||
j->lambdas = outer;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void jw_anonymous_body(jw_t *j, TSNode body, oo_value_t super_value) {
|
||||
@@ -1055,12 +1312,15 @@ static oo_value_t jw_object_creation(jw_t *j, TSNode node) {
|
||||
jw_emit_type_mentions(j, type_node, 0);
|
||||
}
|
||||
int argc = 0;
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc);
|
||||
jw_lambdas_t lambdas = {0};
|
||||
const oo_value_t *args = jw_arguments(j, arguments, &argc, &lambdas);
|
||||
TSNode body = j_child(node, "class_body");
|
||||
if (value.type && !ts_node_is_null(body) && (value.type->flags & OO_F_INTERFACE)) {
|
||||
/* An anonymous implementation of an interface has only Object's constructor. */
|
||||
} else if (value.type) {
|
||||
oo_select_t selected = oo_select_constructor(w, &value, argc, args);
|
||||
if (selected.status == OO_SEL_UNIQUE)
|
||||
jw_finish_lambdas(j, &lambdas, selected.callable, selected.subst, NULL, NULL);
|
||||
if (selected.status == OO_SEL_UNIQUE && selected.callable->graph_qn)
|
||||
oo_emit_call(w, node, selected.callable->graph_qn, CBM_RESOLVE_RULE_LANGUAGE_BINDING,
|
||||
CBM_RESOLVE_FACT_LANGUAGE_BOUND, "java-constructor-selection");
|
||||
@@ -1070,6 +1330,7 @@ static oo_value_t jw_object_creation(jw_t *j, TSNode node) {
|
||||
CBM_RESOLVE_FACT_LANGUAGE_BOUND | CBM_RESOLVE_FACT_CLASS_CALLABLE,
|
||||
"java-implicit-default-constructor");
|
||||
}
|
||||
jw_finish_lambdas(j, &lambdas, NULL, NULL, NULL, NULL);
|
||||
if (!ts_node_is_null(body))
|
||||
jw_anonymous_body(j, body, value);
|
||||
return value;
|
||||
@@ -1083,8 +1344,9 @@ static oo_value_t jw_local_variable(jw_t *j, TSNode node) {
|
||||
if (!j_is(declarator, "variable_declarator"))
|
||||
continue;
|
||||
TSNode value_node = j_field(declarator, "value");
|
||||
oo_value_t initial =
|
||||
ts_node_is_null(value_node) ? oo_value_unknown() : jw_expr(j, value_node);
|
||||
oo_value_t initial = oo_value_unknown();
|
||||
if (!ts_node_is_null(value_node) && !jw_lambda_to_declared(j, value_node, type))
|
||||
initial = jw_expr(j, value_node);
|
||||
jw_bind_declared(j, j_text(&j->text, j_field(declarator, "name")), type, initial,
|
||||
j_dimensions(j_field(declarator, "dimensions")));
|
||||
}
|
||||
@@ -1140,7 +1402,10 @@ static oo_value_t jw_lambda(jw_t *j, TSNode node) {
|
||||
oo_bind(w, j_text(&j->text, parameters), oo_value_unknown());
|
||||
else
|
||||
jw_parameters(j, parameters);
|
||||
TSNode saved_returns = j->returns;
|
||||
j->returns = (TSNode){0};
|
||||
(void)jw_expr(j, j_field(node, "body"));
|
||||
j->returns = saved_returns;
|
||||
oo_frame_pop(w);
|
||||
return oo_value_unknown();
|
||||
}
|
||||
@@ -1279,6 +1544,14 @@ static void jw_local_class(jw_t *j, TSNode node) {
|
||||
|
||||
static oo_value_t jw_expr_kind(jw_t *j, TSNode node, const char *kind) {
|
||||
oo_walk_t *w = j->w;
|
||||
/* `Doc.class` is a Class<Doc>, which is how `<T> T get(Class<T> type)` gets its T. */
|
||||
if (!strcmp(kind, "class_literal") && ts_node_named_child_count(node) == 1) {
|
||||
jw_children(j, node);
|
||||
oo_value_t of = jw_ref(j, j_typeref(&j->text, ts_node_named_child(node, 0)));
|
||||
of.classref = false;
|
||||
oo_value_t klass = oo_value_external("Class");
|
||||
return oo_external_with_element(w, &klass, of);
|
||||
}
|
||||
switch (kind[0]) {
|
||||
case 'i':
|
||||
if (!strcmp(kind, "identifier"))
|
||||
@@ -1432,6 +1705,9 @@ static oo_value_t jw_expr_kind(jw_t *j, TSNode node, const char *kind) {
|
||||
return oo_value_unknown();
|
||||
break;
|
||||
case 'r':
|
||||
if (!strcmp(kind, "return_statement") && ts_node_named_child_count(node) == 1 &&
|
||||
jw_lambda_to_declared(j, ts_node_named_child(node, 0), j->returns))
|
||||
return oo_value_unknown();
|
||||
if (!strcmp(kind, "resource")) {
|
||||
TSNode name = j_field(node, "name");
|
||||
if (ts_node_is_null(name))
|
||||
@@ -1477,7 +1753,7 @@ static oo_value_t jw_expr_kind(jw_t *j, TSNode node, const char *kind) {
|
||||
/* super(...) and this(...) only need their arguments walked. */
|
||||
(void)jw_expr(j, j_field(node, "object"));
|
||||
int argc = 0;
|
||||
(void)jw_arguments(j, j_field(node, "arguments"), &argc);
|
||||
(void)jw_arguments(j, j_field(node, "arguments"), &argc, NULL);
|
||||
return oo_value_unknown();
|
||||
}
|
||||
jw_children(j, node);
|
||||
@@ -1510,6 +1786,9 @@ static void jw_method(jw_t *j, TSNode node) {
|
||||
j_tparams(&j->text, j_field(node, "type_parameters"), &w->scope.tparam_count);
|
||||
oo_frame_t frame;
|
||||
oo_frame_push(w, &frame);
|
||||
TSNode saved_returns = j->returns;
|
||||
j->returns = j_field(node, "type");
|
||||
jw_annotations(j, node);
|
||||
jw_emit_type_mentions(j, j_field(node, "type"), 0);
|
||||
if (j_is(node, "compact_constructor_declaration")) {
|
||||
/* The header's components are this constructor's parameters. */
|
||||
@@ -1519,18 +1798,23 @@ static void jw_method(jw_t *j, TSNode node) {
|
||||
jw_parameters(j, j_field(node, "parameters"));
|
||||
}
|
||||
(void)jw_expr(j, j_field(node, "body"));
|
||||
j->returns = saved_returns;
|
||||
oo_frame_pop(w);
|
||||
w->scope = saved;
|
||||
j->static_method = saved_static;
|
||||
}
|
||||
|
||||
static void jw_field_declaration(jw_t *j, TSNode node) {
|
||||
jw_annotations(j, node);
|
||||
jw_emit_type_mentions(j, j_field(node, "type"), 0);
|
||||
uint32_t count = ts_node_named_child_count(node);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
TSNode declarator = ts_node_named_child(node, i);
|
||||
if (j_is(declarator, "variable_declarator"))
|
||||
(void)jw_expr(j, j_field(declarator, "value"));
|
||||
if (!j_is(declarator, "variable_declarator"))
|
||||
continue;
|
||||
TSNode value = j_field(declarator, "value");
|
||||
if (!jw_lambda_to_declared(j, value, j_field(node, "type")))
|
||||
(void)jw_expr(j, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1553,7 +1837,7 @@ static void jw_class_body(jw_t *j, TSNode body, const oo_type_t *type) {
|
||||
oo_frame_pop(j->w);
|
||||
} else if (j_is(member, "enum_constant")) {
|
||||
int argc = 0;
|
||||
(void)jw_arguments(j, j_field(member, "arguments"), &argc);
|
||||
(void)jw_arguments(j, j_field(member, "arguments"), &argc, NULL);
|
||||
TSNode constant_body = j_field(member, "body");
|
||||
if (!ts_node_is_null(constant_body))
|
||||
jw_anonymous_body(j, constant_body, oo_value_of_type(type));
|
||||
@@ -1573,7 +1857,9 @@ static void jw_type_decl(jw_t *j, TSNode node, const oo_type_t *owner) {
|
||||
return;
|
||||
const char *qn =
|
||||
owner ? j_join(w->arena, owner->qn, name) : j_join(w->arena, w->f->package, name);
|
||||
const oo_type_t *type = qn ? oo_lookup_type(w->ix, qn) : NULL;
|
||||
const oo_type_t *type = qn ? oo_lookup_declared(w->ix, qn, w->rel_path) : NULL;
|
||||
/* A class's annotations read the scope around it. */
|
||||
jw_annotations(j, node);
|
||||
oo_scope_t saved = w->scope;
|
||||
if (type)
|
||||
w->scope.owner = type;
|
||||
@@ -1602,7 +1888,7 @@ void oo_java_resolve(oo_walk_t *w, TSNode root) {
|
||||
oo_file_t *file = cbm_arena_alloc(w->arena, sizeof(*file));
|
||||
if (!file)
|
||||
return;
|
||||
*file = (oo_file_t){.rel = "",
|
||||
*file = (oo_file_t){.rel = w->rel_path ? w->rel_path : "",
|
||||
.package = package,
|
||||
.language = CBM_LANG_JAVA,
|
||||
.imports = imports,
|
||||
|
||||
+197
-26
@@ -210,7 +210,13 @@ static oo_tparam_t *kt_tparams(const kt_text_t *t, TSNode owner, int *out_count)
|
||||
return items;
|
||||
}
|
||||
|
||||
enum { KT_MOD_PRIVATE = 1, KT_MOD_OVERRIDE = 2, KT_MOD_INNER = 4, KT_MOD_ABSTRACT = 8 };
|
||||
enum {
|
||||
KT_MOD_PRIVATE = 1,
|
||||
KT_MOD_OVERRIDE = 2,
|
||||
KT_MOD_INNER = 4,
|
||||
KT_MOD_ABSTRACT = 8,
|
||||
KT_MOD_DATA = 16
|
||||
};
|
||||
|
||||
static unsigned kt_modifiers(const kt_text_t *t, TSNode node) {
|
||||
unsigned flags = 0;
|
||||
@@ -232,6 +238,8 @@ static unsigned kt_modifiers(const kt_text_t *t, TSNode node) {
|
||||
flags |= KT_MOD_INNER;
|
||||
else if (!strcmp(text, "abstract"))
|
||||
flags |= KT_MOD_ABSTRACT;
|
||||
else if (!strcmp(text, "data"))
|
||||
flags |= KT_MOD_DATA;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
@@ -249,7 +257,8 @@ static TSNode kt_after_colon(TSNode node, const char *stop_a, const char *stop_b
|
||||
}
|
||||
if (kt_is(child, stop_a) || (stop_b && kt_is(child, stop_b)))
|
||||
break;
|
||||
if (seen)
|
||||
/* `suspend` and annotations stand in front of the type itself. */
|
||||
if (seen && !kt_is(child, "type_modifiers"))
|
||||
return child;
|
||||
}
|
||||
return (TSNode){0};
|
||||
@@ -346,6 +355,7 @@ static void kh_parameters(const kh_t *h, oo_callable_t *callable, TSNode paramet
|
||||
uint32_t count = ts_node_is_null(parameters) ? 0 : ts_node_child_count(parameters);
|
||||
const oo_typeref_t **refs =
|
||||
count ? cbm_arena_calloc(h->text.arena, count * sizeof(*refs)) : NULL;
|
||||
const char **names = count ? cbm_arena_calloc(h->text.arena, count * sizeof(*names)) : NULL;
|
||||
int total = 0, optional = 0;
|
||||
bool vararg = false, pending_vararg = false;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
@@ -361,6 +371,8 @@ static void kh_parameters(const kh_t *h, oo_callable_t *callable, TSNode paramet
|
||||
type = ts_node_named_child(child, parts - 1);
|
||||
if (refs)
|
||||
refs[total] = kt_typeref(&h->text, type);
|
||||
if (names)
|
||||
names[total] = kt_name(&h->text, kt_child(child, "simple_identifier"));
|
||||
total++;
|
||||
if (pending_vararg && !vararg)
|
||||
callable->vararg_position = total;
|
||||
@@ -376,6 +388,7 @@ static void kh_parameters(const kh_t *h, oo_callable_t *callable, TSNode paramet
|
||||
}
|
||||
}
|
||||
callable->params = refs;
|
||||
callable->param_names = names;
|
||||
callable->param_count = total;
|
||||
callable->min_args = total - optional - (vararg ? 1 : 0);
|
||||
if (callable->min_args < 0)
|
||||
@@ -543,6 +556,7 @@ static void kh_primary_constructor(kh_t *h, oo_type_t *type, TSNode primary) {
|
||||
callable->flags = OO_F_CTOR;
|
||||
kh_parameters(h, callable, primary);
|
||||
oo_index_method(h->ix, type, callable);
|
||||
type->primary = callable;
|
||||
uint32_t count = ts_node_named_child_count(primary);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
TSNode parameter = ts_node_named_child(primary, i);
|
||||
@@ -616,6 +630,8 @@ static void kh_type(kh_t *h, TSNode node, oo_type_t *owner) {
|
||||
flags |= OO_F_PRIVATE;
|
||||
if (modifiers & KT_MOD_INNER)
|
||||
flags |= OO_F_INNER;
|
||||
if (modifiers & KT_MOD_DATA)
|
||||
flags |= OO_F_DATA;
|
||||
if (oo_members_broken(node))
|
||||
flags |= OO_F_INCOMPLETE;
|
||||
const CBMDefinition *def = oo_defs_find(&h->defs, 'T', kt_line(node), name);
|
||||
@@ -886,6 +902,15 @@ static bool kt_hidden(const oo_value_t *value, const char *name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* What an anonymous object's own property holds, seen from outside. Unknown for a function. */
|
||||
static oo_value_t kt_hidden_value(const oo_value_t *value, const char *name) {
|
||||
if (value->hidden && value->hidden->values)
|
||||
for (int i = 0; i < value->hidden->count; i++)
|
||||
if (!strcmp(value->hidden->names[i], name))
|
||||
return value->hidden->values[i];
|
||||
return oo_value_unknown();
|
||||
}
|
||||
|
||||
static oo_select_t kt_select_list(oo_walk_t *w, const kt_list_t *list, int argc,
|
||||
const oo_value_t *args, const oo_value_t *receiver,
|
||||
bool extensions_only) {
|
||||
@@ -1157,6 +1182,18 @@ static oo_value_t kt_summary_first(oo_walk_t *w, const oo_sumstep_t *step, const
|
||||
if (incomplete)
|
||||
return oo_value_unknown();
|
||||
}
|
||||
/* `val at = datetime("at")` on an extension of the enclosing type. */
|
||||
for (int level = 0; step->argc >= 0 && level < KT_LEVEL_COUNT; level++) {
|
||||
kt_list_t list = {.count = 0};
|
||||
kt_level_functions(w, scope, step->name, level, &list);
|
||||
if (list.overflow)
|
||||
return oo_value_unknown();
|
||||
oo_select_t extension = kt_select_list(w, &list, step->argc, NULL, &receiver, true);
|
||||
if (extension.status == OO_SEL_UNIQUE)
|
||||
return oo_callable_return(w, extension.callable, extension.subst);
|
||||
if (extension.status == OO_SEL_AMBIGUOUS)
|
||||
return oo_value_unknown();
|
||||
}
|
||||
if (pass == 0 && type->companion)
|
||||
receiver = oo_value_of_type(type->companion);
|
||||
else
|
||||
@@ -1241,6 +1278,7 @@ typedef struct {
|
||||
kt_text_t text;
|
||||
int depth;
|
||||
kw_args_t *free_args;
|
||||
oo_hidden_t *object_members; /* The anonymous object whose members are being walked. */
|
||||
} kw_t;
|
||||
|
||||
static oo_value_t kw_expr(kw_t *k, TSNode node);
|
||||
@@ -1270,6 +1308,29 @@ static oo_value_t kw_ref(kw_t *k, const oo_typeref_t *ref) {
|
||||
return ref ? oo_value_from_ref(w, ref, &w->scope, NULL) : oo_value_unknown();
|
||||
}
|
||||
|
||||
static void kw_type_mentions(kw_t *k, TSNode node, unsigned depth);
|
||||
|
||||
/* `@InternalApi`, `@OptIn(InternalApi::class)`, in a declaration's modifiers. */
|
||||
static void kw_annotations(kw_t *k, TSNode owner) {
|
||||
TSNode modifiers = kt_child(owner, "modifiers");
|
||||
uint32_t count = ts_node_is_null(modifiers) ? 0 : ts_node_named_child_count(modifiers);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
TSNode annotation = ts_node_named_child(modifiers, i);
|
||||
if (!kt_is(annotation, "annotation"))
|
||||
continue;
|
||||
uint32_t parts = ts_node_named_child_count(annotation);
|
||||
for (uint32_t p = 0; p < parts; p++) {
|
||||
TSNode part = ts_node_named_child(annotation, p);
|
||||
if (kt_is(part, "user_type")) {
|
||||
kw_type_mentions(k, part, 0);
|
||||
} else if (kt_is(part, "constructor_invocation")) {
|
||||
kw_type_mentions(k, kt_child(part, "user_type"), 0);
|
||||
kw_children(k, kt_child(part, "value_arguments"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Every type mention inside an annotation, at the identifier's own span. */
|
||||
static void kw_type_mentions(kw_t *k, TSNode node, unsigned depth) {
|
||||
if (ts_node_is_null(node) || depth > 32)
|
||||
@@ -1467,16 +1528,52 @@ static void kt_stdlib_lambda(oo_walk_t *w, const char *name, const oo_value_t *r
|
||||
}
|
||||
}
|
||||
|
||||
static void kw_bind_destructured(kw_t *k, TSNode node) {
|
||||
/* `componentN` of a destructured value (Kotlin spec 8.4): a pair, a triple or a map entry from the
|
||||
* standard library, a list's element, a data class's primary constructor, or the operator itself.
|
||||
*/
|
||||
static oo_value_t kt_component(oo_walk_t *w, const oo_value_t *value, int position) {
|
||||
if (!value || value->dims)
|
||||
return oo_value_unknown();
|
||||
if (value->external) {
|
||||
const char *dot = strrchr(value->external, '.');
|
||||
const char *leaf = dot ? dot + 1 : value->external;
|
||||
bool tuple = (!strcmp(leaf, "Pair") && value->arg_count == 2) ||
|
||||
(!strcmp(leaf, "Triple") && value->arg_count == 3) ||
|
||||
(!strcmp(leaf, "Entry") && value->arg_count == 2) ||
|
||||
(!strcmp(leaf, "MutableEntry") && value->arg_count == 2);
|
||||
if (tuple)
|
||||
return position < value->arg_count ? value->args[position] : oo_value_unknown();
|
||||
return oo_element_of(w, value);
|
||||
}
|
||||
if (!value->type)
|
||||
return oo_value_unknown();
|
||||
oo_subst_t *subst = oo_subst_of(w, value);
|
||||
char method[32];
|
||||
snprintf(method, sizeof(method), "component%d", position + 1);
|
||||
oo_select_t selected = oo_select_method(w, value, method, 0, NULL, false);
|
||||
if (selected.status == OO_SEL_UNIQUE)
|
||||
return oo_callable_return(w, selected.callable, selected.subst);
|
||||
const oo_callable_t *primary = value->type->primary;
|
||||
if ((value->type->flags & OO_F_DATA) && primary && primary->params &&
|
||||
position < primary->param_count && primary->params[position]) {
|
||||
oo_scope_t scope = oo_scope_of_callable(primary);
|
||||
return oo_value_from_ref(w, primary->params[position], &scope, subst);
|
||||
}
|
||||
return oo_value_unknown();
|
||||
}
|
||||
|
||||
static void kw_bind_destructured(kw_t *k, TSNode node, const oo_value_t *source) {
|
||||
uint32_t count = ts_node_named_child_count(node);
|
||||
int position = 0;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
TSNode child = ts_node_named_child(node, i);
|
||||
if (kt_is(child, "variable_declaration")) {
|
||||
const char *name = kt_name(&k->text, kt_child(child, "simple_identifier"));
|
||||
TSNode declared = kt_after_colon(child, "=", NULL);
|
||||
oo_bind(k->w, name,
|
||||
ts_node_is_null(declared) ? oo_value_unknown()
|
||||
: kw_ref(k, kt_typeref(&k->text, declared)));
|
||||
!ts_node_is_null(declared) ? kw_ref(k, kt_typeref(&k->text, declared))
|
||||
: kt_component(k->w, source, position));
|
||||
position++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1489,7 +1586,9 @@ static oo_value_t kw_lambda(kw_t *k, TSNode lambda, const oo_value_t *expected,
|
||||
oo_frame_push(w, &frame);
|
||||
bool pushed = false;
|
||||
const oo_value_t *function = expected && expected->fn_ref ? expected : NULL;
|
||||
if (function && function->fn_receiver && oo_value_known(function->fn_receiver)) {
|
||||
bool settled = function && !(ctx && oo_ref_unsettled(ctx->callee, function->fn_ref->fn_receiver,
|
||||
ctx->subst));
|
||||
if (settled && function->fn_receiver && oo_value_known(function->fn_receiver)) {
|
||||
pushed = oo_receiver_push(w, *function->fn_receiver, false) != NULL;
|
||||
} else if (function && function->fn_ref->fn_receiver) {
|
||||
/* A receiver exists whose type could not be read. */
|
||||
@@ -1548,7 +1647,7 @@ static oo_value_t kw_lambda(kw_t *k, TSNode lambda, const oo_value_t *expected,
|
||||
oo_bind(w, name, value);
|
||||
index++;
|
||||
} else if (kt_is(parameter, "multi_variable_declaration")) {
|
||||
kw_bind_destructured(k, parameter);
|
||||
kw_bind_destructured(k, parameter, index == 0 ? &first_parameter : NULL);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@@ -1572,6 +1671,7 @@ struct kw_args {
|
||||
TSNode nodes[32];
|
||||
oo_value_t values[32];
|
||||
bool lambda[32];
|
||||
const char *names[32]; /* A named argument lands on that parameter, not at its position. */
|
||||
int count;
|
||||
bool overflow;
|
||||
kw_args_t *next_free;
|
||||
@@ -1621,16 +1721,18 @@ static void kw_collect_args(kw_t *k, TSNode suffix, kw_args_t *args) {
|
||||
continue;
|
||||
}
|
||||
TSNode lambda = kw_lambda_of(argument);
|
||||
uint32_t parts = ts_node_named_child_count(argument);
|
||||
bool named = parts >= 2 && kt_is(ts_node_named_child(argument, 0), "simple_identifier");
|
||||
args->nodes[args->count] = argument;
|
||||
args->lambda[args->count] = !ts_node_is_null(lambda);
|
||||
args->names[args->count] =
|
||||
named ? kt_name(&k->text, ts_node_named_child(argument, 0)) : NULL;
|
||||
args->values[args->count] = oo_value_unknown();
|
||||
args->values[args->count].function_literal = !ts_node_is_null(lambda);
|
||||
args->values[args->count].function_literal = !ts_node_is_null(lambda) && !named;
|
||||
if (ts_node_is_null(lambda)) {
|
||||
uint32_t parts = ts_node_named_child_count(argument);
|
||||
TSNode value = parts ? ts_node_named_child(argument, parts - 1) : (TSNode){0};
|
||||
oo_value_t read = kw_expr(k, value);
|
||||
/* A named argument lands on a parameter of that name, not this position. */
|
||||
if (parts < 2 || !kt_is(ts_node_named_child(argument, 0), "simple_identifier"))
|
||||
if (!named)
|
||||
args->values[args->count] = read;
|
||||
}
|
||||
args->count++;
|
||||
@@ -1642,6 +1744,7 @@ static void kw_collect_args(kw_t *k, TSNode suffix, kw_args_t *args) {
|
||||
continue;
|
||||
args->nodes[args->count] = child;
|
||||
args->lambda[args->count] = true;
|
||||
args->names[args->count] = NULL;
|
||||
args->values[args->count] = oo_value_unknown();
|
||||
args->values[args->count].function_literal = true;
|
||||
args->count++;
|
||||
@@ -1650,9 +1753,15 @@ static void kw_collect_args(kw_t *k, TSNode suffix, kw_args_t *args) {
|
||||
|
||||
/* The parameter a positional or trailing argument lands on. */
|
||||
static const oo_typeref_t *kw_param_for(const oo_callable_t *callee, int index, int total,
|
||||
bool trailing) {
|
||||
bool trailing, const char *named) {
|
||||
if (!callee || !callee->params || !callee->param_count)
|
||||
return NULL;
|
||||
if (named) {
|
||||
for (int i = 0; callee->param_names && i < callee->param_count; i++)
|
||||
if (callee->param_names[i] && !strcmp(callee->param_names[i], named))
|
||||
return callee->params[i];
|
||||
return NULL;
|
||||
}
|
||||
if (trailing && index == total - 1)
|
||||
return callee->params[callee->param_count - 1];
|
||||
return index < callee->param_count
|
||||
@@ -1675,7 +1784,7 @@ static oo_value_t kw_finish_lambdas(kw_t *k, const kw_args_t *args, const oo_cal
|
||||
ctx.subst = subst;
|
||||
oo_value_t expected = oo_value_unknown();
|
||||
bool trailing = kt_is(args->nodes[i], "annotated_lambda");
|
||||
const oo_typeref_t *ref = kw_param_for(callee, i, args->count, trailing);
|
||||
const oo_typeref_t *ref = kw_param_for(callee, i, args->count, trailing, args->names[i]);
|
||||
if (ref && callee) {
|
||||
oo_scope_t scope = oo_scope_of_callable(callee);
|
||||
expected = oo_value_from_ref(w, ref, &scope, subst);
|
||||
@@ -1728,6 +1837,7 @@ static kw_implicit_t kw_implicit_member(kw_t *k, TSNode name_site, const char *n
|
||||
oo_walk_t *w = k->w;
|
||||
kw_implicit_t out = {0};
|
||||
bool no_outer_instance = false;
|
||||
bool own_property = false;
|
||||
for (const oo_receiver_t *r = w->receiver; r; r = r->parent) {
|
||||
bool blocked =
|
||||
no_outer_instance && !(r->value.type && (r->value.type->flags & OO_F_OBJECT));
|
||||
@@ -1735,10 +1845,13 @@ static kw_implicit_t kw_implicit_member(kw_t *k, TSNode name_site, const char *n
|
||||
if (blocked)
|
||||
continue;
|
||||
for (int i = 0; i < r->own_count; i++) {
|
||||
if (!strcmp(r->own_names[i], name)) {
|
||||
if (strcmp(r->own_names[i], name))
|
||||
continue;
|
||||
if (!r->own_function || r->own_function[i]) {
|
||||
out.stop = true;
|
||||
return out;
|
||||
}
|
||||
own_property = true;
|
||||
}
|
||||
if (r->unknown) {
|
||||
out.unknown_receiver = true;
|
||||
@@ -1796,6 +1909,8 @@ static kw_implicit_t kw_implicit_member(kw_t *k, TSNode name_site, const char *n
|
||||
return out;
|
||||
}
|
||||
}
|
||||
/* No function of the name anywhere: the call is the own property's invoke. */
|
||||
out.stop = out.stop || own_property;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1807,6 +1922,14 @@ static oo_select_t kw_free_call(kw_t *k, const char *name, int argc, const oo_va
|
||||
*type_value = oo_value_unknown();
|
||||
oo_typeref_t ref = {.name = name};
|
||||
oo_name_t type_name = oo_resolve_name(w->ix, &w->scope, name);
|
||||
for (const oo_receiver_t *r = w->receiver; r; r = r->parent) {
|
||||
const oo_type_t *nested =
|
||||
r->object_super && r->value.type ? oo_find_nested(w->ix, r->value.type, name) : NULL;
|
||||
if (nested && !(nested->flags & OO_F_AMBIGUOUS)) {
|
||||
type_name = (oo_name_t){.status = OO_NAME_TYPE, .type = nested};
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool shadowed_type = oo_lookup_local(w, cbm_arena_sprintf(w->arena, "#type:%s", name)) != NULL;
|
||||
if (shadowed_type)
|
||||
return (oo_select_t){.status = OO_SEL_AMBIGUOUS};
|
||||
@@ -2124,7 +2247,12 @@ static oo_value_t kw_member_call_with(kw_t *k, TSNode node, TSNode navigation, T
|
||||
kw_lambda_ctx_t ctx = {0};
|
||||
if (!target)
|
||||
kt_stdlib_lambda(w, name, &receiver, &ctx);
|
||||
oo_value_t lambda_value = kw_finish_lambdas(k, args, target, subst, &ctx);
|
||||
/* `Users.insert { ... }`: the receiver settles a `T.() -> Unit`'s T. */
|
||||
const oo_subst_t *lambda_subst =
|
||||
target && !args->overflow
|
||||
? oo_infer_call_subst(w, target, subst, &receiver, kw_arg_values(args), args->count)
|
||||
: subst;
|
||||
oo_value_t lambda_value = kw_finish_lambdas(k, args, target, lambda_subst, &ctx);
|
||||
if (!target) {
|
||||
if (ctx.result_is_lambda)
|
||||
return lambda_value;
|
||||
@@ -2192,6 +2320,7 @@ static oo_value_t kw_object_body(kw_t *k, TSNode object, TSNode members) {
|
||||
oo_receiver_t *receiver = oo_receiver_push(w, value, false);
|
||||
if (receiver) {
|
||||
receiver->unknown = !value.type;
|
||||
receiver->object_super = true;
|
||||
pushed++;
|
||||
}
|
||||
}
|
||||
@@ -2204,28 +2333,41 @@ static oo_value_t kw_object_body(kw_t *k, TSNode object, TSNode members) {
|
||||
uint32_t members_count = ts_node_is_null(members) ? 0 : ts_node_named_child_count(members);
|
||||
const char **names =
|
||||
members_count ? cbm_arena_calloc(w->arena, members_count * sizeof(*names)) : NULL;
|
||||
bool *functions =
|
||||
members_count ? cbm_arena_calloc(w->arena, members_count * sizeof(*functions)) : NULL;
|
||||
oo_value_t *values =
|
||||
members_count ? cbm_arena_calloc(w->arena, members_count * sizeof(*values)) : NULL;
|
||||
int n = 0;
|
||||
for (uint32_t i = 0; i < members_count; i++) {
|
||||
TSNode member = ts_node_named_child(members, i);
|
||||
const char *name = NULL;
|
||||
if (kt_is(member, "function_declaration"))
|
||||
bool function = kt_is(member, "function_declaration");
|
||||
if (function)
|
||||
name = kt_name(&k->text, kt_child(member, "simple_identifier"));
|
||||
else if (kt_is(member, "property_declaration"))
|
||||
name = kt_name(&k->text, kt_child(kt_child(member, "variable_declaration"),
|
||||
"simple_identifier"));
|
||||
if (name && names)
|
||||
if (name && names && functions) {
|
||||
functions[n] = function;
|
||||
names[n++] = name;
|
||||
}
|
||||
}
|
||||
own->own_names = names;
|
||||
own->own_count = n;
|
||||
own->own_function = functions;
|
||||
own->own_values = values;
|
||||
if (hidden) {
|
||||
hidden->names = names;
|
||||
hidden->count = n;
|
||||
hidden->values = values;
|
||||
}
|
||||
}
|
||||
oo_frame_t frame;
|
||||
oo_frame_push(w, &frame);
|
||||
oo_hidden_t *outer_members = k->object_members;
|
||||
k->object_members = hidden;
|
||||
kw_members(k, members, NULL);
|
||||
k->object_members = outer_members;
|
||||
oo_frame_pop(w);
|
||||
for (int i = 0; i < pushed; i++)
|
||||
oo_receiver_pop(w);
|
||||
@@ -2285,13 +2427,16 @@ static oo_value_t kw_identifier(kw_t *k, TSNode node) {
|
||||
continue;
|
||||
for (int i = 0; i < r->own_count; i++)
|
||||
if (!strcmp(r->own_names[i], name))
|
||||
return oo_value_unknown();
|
||||
return r->own_values ? r->own_values[i] : oo_value_unknown();
|
||||
if (r->unknown) {
|
||||
unknown = true;
|
||||
continue;
|
||||
}
|
||||
if (!r->value.type)
|
||||
continue;
|
||||
/* An anonymous object's own member, which has no node. */
|
||||
if (kt_hidden(&r->value, name))
|
||||
return kt_hidden_value(&r->value, name);
|
||||
oo_subst_t *subst = NULL;
|
||||
bool incomplete = false;
|
||||
const oo_field_t *field = oo_find_field(w, &r->value, name, &subst, &incomplete);
|
||||
@@ -2358,7 +2503,7 @@ static oo_value_t kw_navigation(kw_t *k, TSNode node) {
|
||||
size_t head_length = strcspn(text, ".");
|
||||
char *head = cbm_arena_strndup(w->arena, text, head_length);
|
||||
const oo_type_t *exact =
|
||||
head && !oo_lookup_local(w, head) ? oo_lookup_type(w->ix, text) : NULL;
|
||||
head && !oo_lookup_local(w, head) ? oo_lookup_visible(w->ix, text, w->f) : NULL;
|
||||
if (exact && !(exact->flags & OO_F_AMBIGUOUS)) {
|
||||
bool shadow = false;
|
||||
for (const oo_receiver_t *r = w->receiver; r && !shadow; r = r->parent)
|
||||
@@ -2371,7 +2516,9 @@ static oo_value_t kw_navigation(kw_t *k, TSNode node) {
|
||||
}
|
||||
}
|
||||
oo_value_t receiver = kw_expr(k, receiver_node);
|
||||
if (!name || (!receiver.type && !receiver.external) || kt_hidden(&receiver, name))
|
||||
if (name && kt_hidden(&receiver, name))
|
||||
return kt_hidden_value(&receiver, name);
|
||||
if (!name || (!receiver.type && !receiver.external))
|
||||
return oo_value_unknown();
|
||||
if (receiver.type) {
|
||||
oo_subst_t *subst = NULL;
|
||||
@@ -2413,7 +2560,8 @@ static oo_value_t kw_navigation(kw_t *k, TSNode node) {
|
||||
return kt_field_type(w, extension, NULL);
|
||||
}
|
||||
}
|
||||
return oo_value_unknown();
|
||||
/* `entry.value`, `pair.first`. */
|
||||
return receiver.external ? oo_external_member_return(w, &receiver, name) : oo_value_unknown();
|
||||
}
|
||||
|
||||
/* The operator method a desugared expression calls, at the expression's span. */
|
||||
@@ -2682,7 +2830,7 @@ static oo_value_t kw_property(kw_t *k, TSNode node) {
|
||||
initial = value;
|
||||
}
|
||||
if (!ts_node_is_null(multi)) {
|
||||
kw_bind_destructured(k, multi);
|
||||
kw_bind_destructured(k, multi, &initial);
|
||||
} else if (!ts_node_is_null(variable)) {
|
||||
const char *name = kt_name(&k->text, kt_child(variable, "simple_identifier"));
|
||||
TSNode declared = kt_after_colon(variable, "=", NULL);
|
||||
@@ -2715,7 +2863,8 @@ static oo_value_t kw_for(kw_t *k, TSNode node) {
|
||||
}
|
||||
oo_frame_push(w, &frame);
|
||||
if (!ts_node_is_null(multi)) {
|
||||
kw_bind_destructured(k, multi);
|
||||
oo_value_t element = oo_element_of(w, &iterable);
|
||||
kw_bind_destructured(k, multi, &element);
|
||||
} else if (!ts_node_is_null(variable)) {
|
||||
const char *name = kt_name(&k->text, kt_child(variable, "simple_identifier"));
|
||||
TSNode declared = kt_after_colon(variable, "=", NULL);
|
||||
@@ -3171,8 +3320,10 @@ static void kw_members(kw_t *k, TSNode body, const oo_type_t *owner) {
|
||||
TSNode member = ts_node_named_child(body, i);
|
||||
const char *kind = ts_node_type(member);
|
||||
if (!strcmp(kind, "function_declaration")) {
|
||||
kw_annotations(k, member);
|
||||
kw_function(k, member);
|
||||
} else if (!strcmp(kind, "property_declaration")) {
|
||||
kw_annotations(k, member);
|
||||
/* A member property: initializer, accessors, annotation. */
|
||||
oo_frame_t frame;
|
||||
oo_frame_push(w, &frame);
|
||||
@@ -3189,7 +3340,9 @@ static void kw_members(kw_t *k, TSNode body, const oo_type_t *owner) {
|
||||
}
|
||||
}
|
||||
TSNode variable = kt_child(member, "variable_declaration");
|
||||
kw_type_mentions(k, kt_after_colon(variable, "=", NULL), 0);
|
||||
TSNode declared = kt_after_colon(variable, "=", NULL);
|
||||
kw_type_mentions(k, declared, 0);
|
||||
oo_value_t held = oo_value_unknown();
|
||||
uint32_t parts = ts_node_named_child_count(member);
|
||||
for (uint32_t p = 0; p < parts; p++) {
|
||||
TSNode part = ts_node_named_child(member, p);
|
||||
@@ -3220,7 +3373,19 @@ static void kw_members(kw_t *k, TSNode body, const oo_type_t *owner) {
|
||||
kw_children(k, part);
|
||||
continue;
|
||||
}
|
||||
(void)kw_expr(k, part);
|
||||
oo_value_t value = kw_expr(k, part);
|
||||
if (!ts_node_eq(part, declared))
|
||||
held = value;
|
||||
}
|
||||
oo_hidden_t *members = k->object_members;
|
||||
if (members && members->values) {
|
||||
if (!ts_node_is_null(declared))
|
||||
held = kw_ref(k, kt_typeref(&k->text, declared));
|
||||
held.classref = false;
|
||||
const char *held_name = kt_name(&k->text, kt_child(variable, "simple_identifier"));
|
||||
for (int m = 0; held_name && m < members->count; m++)
|
||||
if (!strcmp(members->names[m], held_name))
|
||||
members->values[m] = held;
|
||||
}
|
||||
if (pushed)
|
||||
oo_receiver_pop(w);
|
||||
@@ -3228,7 +3393,10 @@ static void kw_members(kw_t *k, TSNode body, const oo_type_t *owner) {
|
||||
oo_frame_pop(w);
|
||||
} else if (!strcmp(kind, "class_declaration") || !strcmp(kind, "object_declaration") ||
|
||||
!strcmp(kind, "companion_object")) {
|
||||
oo_hidden_t *outer_members = k->object_members;
|
||||
k->object_members = NULL;
|
||||
kw_class_like(k, member, owner);
|
||||
k->object_members = outer_members;
|
||||
} else if (!strcmp(kind, "anonymous_initializer")) {
|
||||
(void)kw_block(k, kt_child(member, "statements"));
|
||||
} else if (!strcmp(kind, "secondary_constructor")) {
|
||||
@@ -3284,7 +3452,9 @@ static void kw_class_like(kw_t *k, TSNode node, const oo_type_t *owner) {
|
||||
return;
|
||||
const char *qn =
|
||||
owner ? kt_join(w->arena, owner->qn, name) : kt_join(w->arena, w->f->package, name);
|
||||
const oo_type_t *type = qn ? oo_lookup_type(w->ix, qn) : NULL;
|
||||
const oo_type_t *type = qn ? oo_lookup_declared(w->ix, qn, w->rel_path) : NULL;
|
||||
/* A class's annotations read the scope around it. */
|
||||
kw_annotations(k, node);
|
||||
bool usable = type && !(type->flags & OO_F_AMBIGUOUS);
|
||||
oo_scope_t saved = w->scope;
|
||||
if (type)
|
||||
@@ -3359,6 +3529,7 @@ static void kw_top_level(kw_t *k, TSNode parent, bool in_error) {
|
||||
} else if (!strcmp(kind, "class_declaration") || !strcmp(kind, "object_declaration")) {
|
||||
kw_class_like(k, child, NULL);
|
||||
} else if (!strcmp(kind, "function_declaration")) {
|
||||
kw_annotations(k, child);
|
||||
kw_function(k, child);
|
||||
} else if (!strcmp(kind, "property_declaration") ||
|
||||
!strcmp(kind, "anonymous_initializer") ||
|
||||
|
||||
@@ -23,6 +23,13 @@ cbm_oo_index_t *cbm_oo_index_new(CBMArena *arena);
|
||||
/* Coordinator only: one file at a time, before seal. */
|
||||
bool cbm_oo_index_add_file(cbm_oo_index_t *index, CBMLanguage language, const char *rel_path,
|
||||
const CBMFileResult *file, const char *source, int source_len);
|
||||
/* Coordinator only, between files: every later file belongs to the build project in this
|
||||
* directory (a .csproj, build.gradle or pom.xml beside it or above it). NULL: no known project. */
|
||||
void cbm_oo_index_set_project(cbm_oo_index_t *index, const char *project_dir);
|
||||
/* Coordinator only, between files: every later file is a reference assembly's
|
||||
* source. Such a declaration is the contract of the implementation of its name,
|
||||
* which stands for it. Call it after the implementation files are in. */
|
||||
void cbm_oo_index_begin_reference_files(cbm_oo_index_t *index);
|
||||
/* Resolves supertypes. After this the index is immutable and shared by readers. */
|
||||
void cbm_oo_index_seal(cbm_oo_index_t *index);
|
||||
void cbm_oo_index_free(cbm_oo_index_t *index);
|
||||
|
||||
@@ -414,12 +414,26 @@ bool cbm_resolve_capabilities(CBMLanguage language, cbm_resolve_capabilities_t *
|
||||
out->limitation =
|
||||
"The declaration static rules select: lexical scopes, import precedence, inherited "
|
||||
"members over resolved supertypes, arity, known argument types and exact overload "
|
||||
"matches. Competing overloads on different nodes, lambdas with an unreadable "
|
||||
"matches. A lambda's parameters come from the selected declaration's functional type, "
|
||||
"and a generic method's type arguments from what one argument settles. A name several "
|
||||
"declarations claim means the one in the file itself or the only one in the file's "
|
||||
"build project; any other reader abstains. A C# reference assembly's declaration or "
|
||||
"platform stub stands behind the implementation of its name, and a partial type's "
|
||||
"mentions target the node of the file named after it. A function or extension method "
|
||||
"in "
|
||||
"another project's test sources is no candidate. Competing overloads on different "
|
||||
"nodes (one signature in two platform files among them), lambdas with an unreadable "
|
||||
"receiver, members hidden by an unindexed supertype and unmodelled inference abstain. "
|
||||
"A type-shaped name is assumed not to be a field of an unindexed supertype. Implicit "
|
||||
"iterator and componentN calls share one span and are not emitted. A bare call inside "
|
||||
"an extension on an external type abstains, because that type's members are unlisted. "
|
||||
"A C# file the grammar cannot read is read without its later conditional branches.";
|
||||
"A C# file the grammar cannot read is read without its later conditional branches, and "
|
||||
"with the spellings it loses members to made readable at the same spans. A C# local "
|
||||
"function's body belongs to the member around it, a property's to the property, and a "
|
||||
"conversion operator and an indexer have nodes of their own. C# member lookup: an "
|
||||
"explicit interface implementation and an interface's members are no class's, an "
|
||||
"applicable method hides the base type's, and an identical parameter is the better "
|
||||
"conversion.";
|
||||
break;
|
||||
case CBM_LANG_RUST:
|
||||
out->adapter = "rust";
|
||||
|
||||
@@ -337,6 +337,7 @@ static const ext_entry_t EXT_TABLE[] = {
|
||||
* .NET repository's package references, target frameworks, build hooks and
|
||||
* project layout — none of which were reachable while these were unmapped. */
|
||||
{".csproj", CBM_LANG_XML},
|
||||
{".projitems", CBM_LANG_XML},
|
||||
{".vbproj", CBM_LANG_XML},
|
||||
{".fsproj", CBM_LANG_XML},
|
||||
{".props", CBM_LANG_XML},
|
||||
|
||||
+144
-14
@@ -2494,6 +2494,129 @@ void cbm_pxc_dispatch_file_with_php_bindings(
|
||||
rust_shared_ctx, bindings, NULL);
|
||||
}
|
||||
|
||||
/* The .NET convention for a reference assembly: <library>/ref holds a project of its own beside
|
||||
* a <library>/src tree. Both compile one assembly name, so a type they share is one type: the
|
||||
* reference declares its contract, the implementation (there, or forwarded to another project)
|
||||
* its members. Returns the set of such `ref` directories; each path adds its own segments,
|
||||
* so the cost is the length of the file list. NULL when the repository has none. */
|
||||
static CBMHashTable *pxc_csharp_reference_dirs(CBMArena *arena, const cbm_file_info_t *files,
|
||||
int file_count) {
|
||||
CBMHashTable *libraries = NULL, *references = NULL;
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
const char *rel = files[i].rel_path;
|
||||
if (files[i].language != CBM_LANG_CSHARP || !rel)
|
||||
continue;
|
||||
for (const char *at = strstr(rel, "/src/"); at; at = strstr(at + 1, "/src/")) {
|
||||
if (!libraries)
|
||||
libraries = cbm_ht_create(64);
|
||||
const char *library =
|
||||
libraries ? cbm_arena_strndup(arena, rel, (size_t)(at - rel)) : NULL;
|
||||
if (library && !cbm_ht_get(libraries, library))
|
||||
cbm_ht_set(libraries, library, (void *)library);
|
||||
}
|
||||
}
|
||||
for (int i = 0; libraries && i < file_count; i++) {
|
||||
const char *rel = files[i].rel_path;
|
||||
size_t length = rel ? strlen(rel) : 0;
|
||||
if (length < sizeof(".csproj") || strcmp(rel + length - (sizeof(".csproj") - 1), ".csproj"))
|
||||
continue;
|
||||
const char *slash = strrchr(rel, '/');
|
||||
const char *dir = slash ? cbm_arena_strndup(arena, rel, (size_t)(slash - rel)) : NULL;
|
||||
const char *base = dir ? strrchr(dir, '/') : NULL;
|
||||
if (!base || strcmp(base, "/ref"))
|
||||
continue;
|
||||
const char *library = cbm_arena_strndup(arena, dir, (size_t)(base - dir));
|
||||
if (!library || !cbm_ht_get(libraries, library))
|
||||
continue;
|
||||
if (!references)
|
||||
references = cbm_ht_create(64);
|
||||
if (references && !cbm_ht_get(references, dir))
|
||||
cbm_ht_set(references, dir, (void *)dir);
|
||||
}
|
||||
cbm_ht_free(libraries);
|
||||
return references;
|
||||
}
|
||||
|
||||
/* A declaration that is the contract of an implementation: a reference assembly's, or one in a
|
||||
* `<Type>.PlatformNotSupported.cs`, the .NET convention for what a target without the feature
|
||||
* compiles instead, with every body throwing. A reference directory's ancestry is bounded by the
|
||||
* path's depth. */
|
||||
static bool pxc_in_reference_dir(CBMArena *arena, const CBMHashTable *references, const char *rel) {
|
||||
static const char STUB[] = ".PlatformNotSupported.cs";
|
||||
size_t length = rel ? strlen(rel) : 0;
|
||||
if (length >= sizeof(STUB) && !strcmp(rel + length - (sizeof(STUB) - 1), STUB))
|
||||
return true;
|
||||
if (!references || !rel)
|
||||
return false;
|
||||
char *dir = cbm_arena_strdup(arena, rel);
|
||||
for (char *slash = dir ? strrchr(dir, '/') : NULL; slash; slash = strrchr(dir, '/')) {
|
||||
*slash = '\0';
|
||||
if (cbm_ht_get(references, dir))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The directories holding a build project's manifest. One pass over the file list. "." is the root.
|
||||
*/
|
||||
static CBMHashTable *pxc_project_dirs(CBMArena *arena, const cbm_file_info_t *files,
|
||||
int file_count) {
|
||||
CBMHashTable *dirs = cbm_ht_create(64);
|
||||
for (int i = 0; dirs && i < file_count; i++) {
|
||||
const char *rel = files[i].rel_path;
|
||||
const char *slash = rel ? strrchr(rel, '/') : NULL;
|
||||
const char *base = slash ? slash + 1 : rel;
|
||||
size_t length = base ? strlen(base) : 0;
|
||||
bool manifest = base && (!strcmp(base, "build.gradle") ||
|
||||
!strcmp(base, "build.gradle.kts") || !strcmp(base, "pom.xml") ||
|
||||
(length > 7 && !strcmp(base + length - 7, ".csproj")) ||
|
||||
(length > 10 && !strcmp(base + length - 10, ".projitems")));
|
||||
if (!manifest)
|
||||
continue;
|
||||
const char *dir = slash ? cbm_arena_strndup(arena, rel, (size_t)(slash - rel)) : ".";
|
||||
if (dir && !cbm_ht_get(dirs, dir))
|
||||
cbm_ht_set(dirs, dir, (void *)dir);
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
/* The nearest manifest's directory above this file. Bounded by the path's depth. */
|
||||
static const char *pxc_project_of(CBMArena *arena, const CBMHashTable *dirs, const char *rel) {
|
||||
if (!dirs || !rel)
|
||||
return NULL;
|
||||
char *dir = cbm_arena_strdup(arena, rel);
|
||||
for (char *slash = dir ? strrchr(dir, '/') : NULL; slash; slash = strrchr(dir, '/')) {
|
||||
*slash = '\0';
|
||||
const char *project = cbm_ht_get(dirs, dir);
|
||||
if (project)
|
||||
return project;
|
||||
}
|
||||
return cbm_ht_get(dirs, ".");
|
||||
}
|
||||
|
||||
static void pxc_oo_add_file(const cbm_pipeline_ctx_t *ctx, cbm_oo_index_t *index,
|
||||
CBMFileResult **cache, const cbm_file_info_t *files, int i,
|
||||
CBMArena *scratch, const CBMHashTable *projects) {
|
||||
bool loaded = false;
|
||||
CBMFileResult *result = cbm_pipeline_result_acquire(ctx, cache, i, NULL, &loaded);
|
||||
if (!result)
|
||||
return;
|
||||
int source_len = result->source_len;
|
||||
const char *source = result->source;
|
||||
char *owned = NULL;
|
||||
if (!source || source_len <= 0) {
|
||||
owned = pxc_read_file(files[i].path, &source_len);
|
||||
source = owned;
|
||||
}
|
||||
if (source && source_len > 0) {
|
||||
cbm_oo_index_set_project(index, pxc_project_of(scratch, projects, files[i].rel_path));
|
||||
(void)cbm_oo_index_add_file(index, files[i].language, files[i].rel_path, result, source,
|
||||
source_len);
|
||||
}
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, owned);
|
||||
cbm_pipeline_result_release(result, loaded);
|
||||
}
|
||||
|
||||
struct cbm_oo_index *cbm_pxc_build_oo_index(const cbm_pipeline_ctx_t *ctx, CBMArena *arena,
|
||||
CBMFileResult **cache, const cbm_file_info_t *files,
|
||||
int file_count) {
|
||||
@@ -2505,26 +2628,33 @@ struct cbm_oo_index *cbm_pxc_build_oo_index(const cbm_pipeline_ctx_t *ctx, CBMAr
|
||||
cbm_oo_index_t *index = cbm_oo_index_new(arena);
|
||||
if (!index)
|
||||
return NULL;
|
||||
CBMArena scratch;
|
||||
cbm_arena_init(&scratch);
|
||||
CBMHashTable *references = pxc_csharp_reference_dirs(&scratch, files, file_count);
|
||||
CBMHashTable *projects = pxc_project_dirs(&scratch, files, file_count);
|
||||
bool *deferred = cbm_arena_calloc(&scratch, (size_t)file_count * sizeof(bool));
|
||||
bool any_deferred = false;
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
if (!cbm_oo_language(files[i].language))
|
||||
continue;
|
||||
bool loaded = false;
|
||||
CBMFileResult *result = cbm_pipeline_result_acquire(ctx, cache, i, NULL, &loaded);
|
||||
if (!result)
|
||||
if (deferred && files[i].language == CBM_LANG_CSHARP &&
|
||||
pxc_in_reference_dir(&scratch, references, files[i].rel_path)) {
|
||||
deferred[i] = true;
|
||||
any_deferred = true;
|
||||
continue;
|
||||
int source_len = result->source_len;
|
||||
const char *source = result->source;
|
||||
char *owned = NULL;
|
||||
if (!source || source_len <= 0) {
|
||||
owned = pxc_read_file(files[i].path, &source_len);
|
||||
source = owned;
|
||||
}
|
||||
if (source && source_len > 0)
|
||||
(void)cbm_oo_index_add_file(index, files[i].language, files[i].rel_path, result, source,
|
||||
source_len);
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, owned);
|
||||
cbm_pipeline_result_release(result, loaded);
|
||||
pxc_oo_add_file(ctx, index, cache, files, i, &scratch, projects);
|
||||
}
|
||||
/* An implementation is in before any contract asks whether it exists. */
|
||||
if (any_deferred) {
|
||||
cbm_oo_index_begin_reference_files(index);
|
||||
for (int i = 0; i < file_count; i++)
|
||||
if (deferred[i])
|
||||
pxc_oo_add_file(ctx, index, cache, files, i, &scratch, projects);
|
||||
}
|
||||
cbm_ht_free(references);
|
||||
cbm_ht_free(projects);
|
||||
cbm_arena_destroy(&scratch);
|
||||
cbm_oo_index_seal(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
+12
-10
@@ -235,7 +235,7 @@ static const struct {
|
||||
static bool dm_namespace_source(char *out, size_t cap, size_t index, bool file_scoped) {
|
||||
const char *recovery = dm_namespace_cases[index].recovery
|
||||
? "public class ParseEdge { unsafe void M(void* p) { "
|
||||
"_r = ref *(int*)p; } }\n"
|
||||
"_r = ref * (int*)p; } }\n"
|
||||
: "";
|
||||
int n = snprintf(out, cap,
|
||||
"namespace %s%s"
|
||||
@@ -509,7 +509,7 @@ TEST(doc_mentions_cs_namespace_boundaries) {
|
||||
const char *id, *header, *footer, *name;
|
||||
} cases[] = {
|
||||
{"bare_keyword", "namespace class{\n",
|
||||
"public class ParseEdge { unsafe void M(void* p) { _r = ref *(int*)p; } }\n}\n", NULL},
|
||||
"public class ParseEdge { unsafe void M(void* p) { _r = ref * (int*)p; } }\n}\n", NULL},
|
||||
{"adjacent_file", "namespace@Boundary;\n", "", "Boundary"},
|
||||
{"adjacent_block", "namespace@Boundary {\n", "}\n", "Boundary"},
|
||||
{"verbatim_keyword_file", "namespace @class;\n", "", "class"},
|
||||
@@ -1001,14 +1001,15 @@ TEST(doc_mentions_cs_utf8_surfaces) {
|
||||
* recovery closes blocks early and late, which would otherwise move the
|
||||
* declarations after the error into another namespace or outer type. */
|
||||
TEST(doc_mentions_cs_scope_parse_errors) {
|
||||
/* `ref *(int*)p` is beyond the vendored grammar: the method swallows the
|
||||
/* `ref * (int*)p` is beyond the vendored grammar, and the parse view reads it
|
||||
* only without the space: the method swallows the
|
||||
* class's closing brace, `P` becomes a local, `Two` a nested class of
|
||||
* `One`, and the namespace an error node. */
|
||||
const char *late = "namespace A\n" /* 1 */
|
||||
"{\n" /* 2 */
|
||||
" public class One\n" /* 3 */
|
||||
" {\n" /* 4 */
|
||||
" unsafe void M(void* p) { _r = ref *(int*)p; }\n" /* 5 */
|
||||
" unsafe void M(void* p) { _r = ref * (int*)p; }\n" /* 5 */
|
||||
" public int P;\n" /* 6 */
|
||||
" }\n" /* 7 */
|
||||
" public class Two { }\n" /* 8 */
|
||||
@@ -1121,7 +1122,8 @@ TEST(doc_mentions_cs_scope_parse_errors) {
|
||||
s = r->doc_scope;
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_NOT_NULL(strstr(s, "R\t1\t0\t1\t9\tNet\n"));
|
||||
ASSERT_NOT_NULL(strstr(s, "T\t1\t3\t7\tc!\t-\tVerb\t\t"));
|
||||
/* The parse view blanks the doubled quotes, so nothing of Verb is hidden. */
|
||||
ASSERT_NOT_NULL(strstr(s, "T\t1\t3\t7\tc\t-\tVerb\t\t"));
|
||||
ASSERT_NOT_NULL(strstr(s, "T\t1\t8\t8\tc"));
|
||||
ASSERT_NOT_NULL(strstr(s, "\tAfter\t\t"));
|
||||
ASSERT_NULL(strstr(s, "\nX\t"));
|
||||
@@ -1171,12 +1173,12 @@ TEST(doc_mentions_cs_scope_parse_errors) {
|
||||
cbm_free_result(r);
|
||||
|
||||
/* A member whose header did not parse is hidden, and its type says so:
|
||||
* `safe extern` comes back as a method named `extern`. */
|
||||
* an unknown modifier comes back as a return type, and `extern` as the method. */
|
||||
const char *hidden = "namespace C\n"
|
||||
"{\n"
|
||||
" public class Holey\n" /* 3 */
|
||||
" {\n"
|
||||
" public safe extern int Hidden();\n" /* 5 */
|
||||
" public fancy extern int Hidden();\n" /* 5 */
|
||||
" public int Seen;\n" /* 6 */
|
||||
" }\n" /* 7 */
|
||||
"}\n";
|
||||
@@ -3917,7 +3919,7 @@ static void dm_write_resolver_fixture(const char *tmp) {
|
||||
" }\n"
|
||||
" public class Holey\n"
|
||||
" {\n"
|
||||
" public safe extern int Hidden();\n"
|
||||
" public fancy extern int Hidden();\n"
|
||||
" /// <summary><see cref=\"Hidden\"/> <see cref=\"int\"/></summary>\n"
|
||||
" public int Seen;\n"
|
||||
" }\n"
|
||||
@@ -6037,7 +6039,7 @@ TEST(doc_mentions_cs_scan_nested_holes) {
|
||||
"{\n"
|
||||
" public class Deep\n" /* 3 */
|
||||
" {\n"
|
||||
" unsafe void E(void* p) { _r = ref *(int*)p; }\n"
|
||||
" unsafe void E(void* p) { _r = ref * (int*)p; }\n"
|
||||
" void M() { s = $\"a{$\"b{$\"c{1}\"}\"}\"; }\n"
|
||||
" public int Q;\n" /* 7 */
|
||||
" }\n" /* 8 */
|
||||
@@ -6060,7 +6062,7 @@ TEST(doc_mentions_cs_scan_nested_holes) {
|
||||
"{\n"
|
||||
" public class Deep\n"
|
||||
" {\n"
|
||||
" unsafe void E(void* p) { _r = ref *(int*)p; }\n"
|
||||
" unsafe void E(void* p) { _r = ref * (int*)p; }\n"
|
||||
" void M() { s = ",
|
||||
"$\"{", DM_DEEP, closers);
|
||||
free(closers);
|
||||
|
||||
@@ -3511,14 +3511,11 @@ TEST(parallel_csharp_same_name_class_binds_by_namespace) {
|
||||
.site = {.file = rels[11], .caller = "PartUser.Use", .needle = "new Thing()"},
|
||||
.relation = "CALLS",
|
||||
.target = {.file = rels[9],
|
||||
.suffix = "cbm_cs_same_name.src.Parts.Thing.A.Thing",
|
||||
.label = "Class",
|
||||
.declaration_line = 2,
|
||||
.declaration_text = "public partial class Thing { public void Alpha() { } }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "csharp-partial-type-identity",
|
||||
.why = "Each partial piece of Thing has a node of its own (its QN carries its file), so "
|
||||
"no one class node is the type this constructor call selects.",
|
||||
.tried = "The members bind to the piece that declares them. The type itself has no single "
|
||||
"identity to bind to, and choosing a piece would be a guess."};
|
||||
.mode = RQ_REQUIRED};
|
||||
RQ_Report quality[2] = {0};
|
||||
bool ok[2][N_CHECKS];
|
||||
bool all = true;
|
||||
@@ -3528,8 +3525,8 @@ TEST(parallel_csharp_same_name_class_binds_by_namespace) {
|
||||
g ? "csharp-members-parallel" : "csharp-members-sequential",
|
||||
sources, N_FILES, expected, N_EXPECTED, &quality[g]));
|
||||
ASSERT_EQ(quality[g].genuine, N_EXPECTED);
|
||||
ASSERT_EQ(quality[g].found, N_EXPECTED - 1);
|
||||
ASSERT_EQ(quality[g].missing, 1);
|
||||
ASSERT_EQ(quality[g].found, N_EXPECTED);
|
||||
ASSERT_EQ(quality[g].missing, 0);
|
||||
ok[g][0] = has_edge_from_callable_to_qn(gb, "Handler.Handle", bo, "CALLS");
|
||||
ok[g][1] = !has_edge_from_callable_to_qn(gb, "Handler.Handle", pf, "CALLS");
|
||||
ok[g][2] = has_edge_from_callable_to_qn(gb, "PlatformHandler.Handle", pf, "CALLS");
|
||||
|
||||
@@ -630,7 +630,9 @@ static const char KOTLIN_DIRECT_8[] =
|
||||
"package duplicate\n"
|
||||
"class Duplicate {\n"
|
||||
" fun same(value: Int): Int = value\n"
|
||||
"}\n"
|
||||
"}\n";
|
||||
static const char KOTLIN_DIRECT_8_USE[] =
|
||||
"package duplicate\n"
|
||||
"fun invalid(value: Duplicate, number: Int): Int = value.same(number)\n";
|
||||
static const char KOTLIN_DIRECT_9[] = "package duplicate\n"
|
||||
"class Duplicate {\n"
|
||||
@@ -976,7 +978,7 @@ static const RQ_Forbidden KOTLIN_DIRECT_FORBIDDEN[] = {
|
||||
.relation = "CALLS",
|
||||
.any_target = true},
|
||||
{.case_id = "duplicate-class-domain",
|
||||
.site = {.file = "Duplicate.kt",
|
||||
.site = {.file = "DuplicateUse.kt",
|
||||
.caller = "invalid",
|
||||
.needle = "value.same(number)",
|
||||
.occurrence_index = 0},
|
||||
@@ -1000,6 +1002,7 @@ TEST(resolver_kotlin_primitive_arguments_and_lexical_members_require_proofs) {
|
||||
{.path = "Shadow.kt", .text = KOTLIN_DIRECT_6},
|
||||
{.path = "Alias.kt", .text = KOTLIN_DIRECT_7},
|
||||
{.path = "Duplicate.kt", .text = KOTLIN_DIRECT_8},
|
||||
{.path = "DuplicateUse.kt", .text = KOTLIN_DIRECT_8_USE},
|
||||
{.path = "DuplicateOther.kt", .text = KOTLIN_DIRECT_9},
|
||||
{.path = "Decoy.kt", .text = KOTLIN_DIRECT_10},
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user