fix(resolver): Kotlin extension receivers take standard subtypes; vararg need not be last

An extension on Iterable or Collection applies to a List, a Set or a project Iterable, and to nothing else. Past a vararg, position no longer says which parameter an argument lands on, so it no longer excludes. A project class is never an Array.

resolver_oo: 7 pass, 0 fail.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-01 16:55:51 +01:00
1 parent a401ffa979
commit 95053d34e7
4 files changed
+73 -3

No files matched your search

+8
View File
@@ -1176,6 +1176,10 @@ static bool oo_argument_excludes(oo_walk_t *w, const oo_callable_t *callable,
return true;
if (callable->max_args < 0 && index == callable->param_count - 1)
continue; /* Variadic element versus array conversion is not modelled. */
/* Past a vararg, position no longer says which parameter an argument lands on. */
if (callable->vararg_position && i >= callable->vararg_position - 1 &&
!args[i].function_literal)
continue;
if (oo_ref_is_tparam(callable, callable->params[index]))
continue;
oo_value_t parameter = oo_value_from_ref(w, callable->params[index], &scope, subst);
@@ -1189,6 +1193,10 @@ static bool oo_argument_excludes(oo_walk_t *w, const oo_callable_t *callable,
return true;
continue;
}
/* Array is final: a project class is never one. */
if (parameter.external && !strcmp(oo_leaf(parameter.external), "Array") && argument->type &&
oo_hierarchy_complete(w->ix, argument->type))
return true;
if (parameter.fn_ref) {
/* A value whose whole hierarchy is known is not a function. */
if (argument->type && oo_hierarchy_complete(w->ix, argument->type))
+1
View File
@@ -94,6 +94,7 @@ typedef struct oo_callable {
const oo_file_t *file;
uint32_t flags;
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;
int param_count;
const oo_typeref_t *ret;
+44 -3
View File
@@ -362,6 +362,8 @@ static void kh_parameters(const kh_t *h, oo_callable_t *callable, TSNode paramet
if (refs)
refs[total] = kt_typeref(&h->text, type);
total++;
if (pending_vararg && !vararg)
callable->vararg_position = total;
vararg |= pending_vararg;
pending_vararg = false;
/* A class parameter keeps its default inside its own node. */
@@ -797,6 +799,41 @@ static bool kt_explicit_external(oo_walk_t *w, const oo_scope_t *scope, const ch
return false;
}
/* The standard library's collection and text subtyping, for extension receivers. */
static bool kt_external_conforms(const char *actual, const char *declared) {
static const struct {
const char *actual, *supertypes;
} TABLE[] = {
{"List", " Collection Iterable "},
{"Set", " Collection Iterable "},
{"Collection", " Iterable "},
{"MutableList", " List Collection MutableCollection Iterable MutableIterable "},
{"MutableSet", " Set Collection MutableCollection Iterable MutableIterable "},
{"MutableCollection", " Collection Iterable MutableIterable "},
{"ArrayList", " MutableList List Collection MutableCollection Iterable "},
{"LinkedHashSet", " MutableSet Set Collection MutableCollection Iterable "},
{"HashSet", " MutableSet Set Collection MutableCollection Iterable "},
{"MutableMap", " Map "},
{"HashMap", " MutableMap Map "},
{"LinkedHashMap", " MutableMap Map HashMap "},
{"String", " CharSequence Comparable "},
{"Int", " Number Comparable "},
{"Long", " Number Comparable "},
{"Double", " Number Comparable "},
{"Float", " Number Comparable "},
{"Short", " Number Comparable "},
{"Byte", " Number Comparable "},
};
char word[64];
int written = snprintf(word, sizeof(word), " %s ", declared);
if (written <= 0 || (size_t)written >= sizeof(word))
return false;
for (size_t i = 0; i < sizeof(TABLE) / sizeof(TABLE[0]); i++)
if (!strcmp(TABLE[i].actual, actual))
return strstr(TABLE[i].supertypes, word) != NULL;
return false;
}
/* Does an extension's declared receiver accept the value? */
static bool kt_ref_accepts(oo_walk_t *w, const oo_typeref_t *ref, oo_scope_t scope,
const oo_value_t *value) {
@@ -823,11 +860,15 @@ static bool kt_ref_accepts(oo_walk_t *w, const oo_typeref_t *ref, oo_scope_t sco
declared = declared ? declared + 1 : receiver.external;
if (!strcmp(declared, "Any"))
return value->type || value->external;
if (!value->external)
return false;
/* A project collection reaches Iterable through its declared supertypes. */
if (!value->external) {
oo_value_t element =
!strcmp(declared, "Iterable") ? oo_element_of(w, value) : oo_value_unknown();
return oo_value_known(&element);
}
const char *actual = strrchr(value->external, '.');
actual = actual ? actual + 1 : value->external;
return !strcmp(declared, actual);
return !strcmp(declared, actual) || kt_external_conforms(actual, declared);
}
return false;
}
+20
View File
@@ -320,6 +320,12 @@ static const OOFile KOTLIN_FILES[] = {
"private fun fileOnly(): Int = 5\n"
"val topProperty: Store = Store()\n"
"val Store.extProp: Store get() = this\n"
"fun <T> Iterable<T>.onIterable(): Int = 1\n"
"fun Collection<Int>.onCollection(): Int = 1\n"
"class Rows : Iterable<Store> {\n"
" override fun iterator(): Iterator<Store> = TODO()\n"
"}\n"
"fun <T> middleVararg(first: Int, vararg rest: Store, block: () -> Unit): Int = 1\n"
"fun String.shout(): String = this\n"
"fun Int.onlyInt(): Int = this\n"},
{"core/Tx.kt",
@@ -378,6 +384,12 @@ static const OOFile KOTLIN_FILES[] = {
" fun externalExtension() { text?.shout() }\n"
" fun externalWrongReceiver() { text?.onlyInt() }\n"
" fun extensionProperty(s: Store) { s.extProp.get() }\n"
" fun listIsIterable(l: List<Int>) { l.onIterable() }\n"
" fun projectIsIterable(r: Rows) { r.onIterable() }\n"
" fun setIsCollection(v: Set<Int>) { v.onCollection() }\n"
" fun stringIsNoCollection(t: String) { t.onCollection() }\n"
" fun storeIsNoIterable(s: Store) { s.onIterable() }\n"
" fun varargInTheMiddle(s: Store) { middleVararg<Int>(1, s, s) { } }\n"
" fun wrongExtensionProperty(s: Store) { s.otherProp }\n"
" fun objectValue() { val t = object : Store() { fun own() = 1 }; t.get(); t.ext() }\n"
" fun objectOwnMember() {\n"
@@ -495,6 +507,14 @@ static int oo_kotlin_check(bool parallel) {
/* An extension on a value whose type lies outside the project. */
OO_EXPECT("CALLS", "Main.externalExtension", "Store.shout", 1);
OO_EXPECT("CALLS", "Main.externalWrongReceiver", "Store.onlyInt", 0);
/* An extension's receiver takes the standard library's subtypes, and only those. */
OO_EXPECT("CALLS", "Main.listIsIterable", "Store.onIterable", 1);
OO_EXPECT("CALLS", "Main.projectIsIterable", "Store.onIterable", 1);
OO_EXPECT("CALLS", "Main.setIsCollection", "Store.onCollection", 1);
OO_EXPECT("CALLS", "Main.stringIsNoCollection", "Store.onCollection", 0);
OO_EXPECT("CALLS", "Main.storeIsNoIterable", "Store.onIterable", 0);
/* Past a vararg, position does not say which parameter an argument lands on. */
OO_EXPECT("CALLS", "Main.varargInTheMiddle", "Store.middleVararg", 1);
/* Extension properties: only the receiver they are declared on. */
OO_EXPECT("USAGE", "Main.extensionProperty", "Store.extProp", 1);
OO_EXPECT("CALLS", "Main.extensionProperty", "Store.Store.get", 1);