MethodOverloadResolver Implementation
Il2Cpp:
marshal/MethodOverloadResolver.cppMono: Phase 3Emit/+ planned managed resolver (semantics aligned) Normative authority: ../../spec/04-METHOD-OVERLOAD.md — this doc only covers runtime data structures and C++ landing points
1. Call chain
- Lua calls a dispatch closure (not a direct method closure).
- Dispatch closure upvalue carries
MethodGroups*(MetaBinding::CreateMethodDispatchClosureRef). MethodOverloadResolver::Resolve(L, groups, argStart, argCount)returns a uniqueMethodMarshalCtx*or errors.MethodBridge::InvokeLua2Cs(L, target, argStart, ctx)runs the selected overload.
Direct closures (single overload) do not go through the Resolver.
2. MethodGroups bucket structure
Defined in MarshalDefs.h:
constexpr size_t kMaxSmallArgCount = 4;
struct MethodGroup {
const MethodMarshalCtx** methods;
size_t methodCount;
};
struct MethodGroups {
const MethodGroup* smallArgCountMethodGroups[kMaxSmallArgCount + 1];
const MethodMarshalCtx** largeArgCountMethods;
size_t largeArgCountMethodCount;
};
Bind-time bucketing: Group same-name overloads by parameter count (excluding this). Resolve first O(1)-fetches groups->smallArgCountMethodGroups[argCount]; if empty, linearly scans largeArgCountMethods.
Order within the same argCount bucket = Codegen declaration order (spec §3.2 tie-break).
3. ConversionKind and Better Match
enum class ConversionKind : uint8_t {
None, Identity, ImplicitNumeric, ImplicitEnum, NullLiteral,
ImplicitReference, ImplicitExtendedInteger, ImplicitBoxing,
ImplicitArray, NotConvertible,
};
struct MethodOverloadResolutionResult {
MethodOverloadResolutionKind kind; // None / BestMatch / Ambiguous
const MethodMarshalCtx* method;
};
GetConversionKind(L, stackIndex, paramMeta) (MethodOverloadResolver.cpp):
- Reads Lua stack type + target
Il2CppType*fromMarshalMetaInfo; - Returns conversion category from that argument to the parameter;
- Matches spec §3.3 / §3.6 better function member rules (Identity beats ImplicitBoxing, etc.).
Per-arg compare: Check applicability for each candidate in the bucket; pick best among applicable; ties use declaration order.
4. Resolve algorithm outline
MethodOverloadResolutionResult MethodOverloadResolver::Resolve(
lua_State* L, const MethodGroups* groups, int32_t argStart, int32_t argCount)
{
// 1. Fetch MethodGroup* by argCount
// 2. Walk methods[]:
// - param count / params array / optional rules
// - GetConversionKind per arg; NotConvertible → skip
// - CompareConversionKind update best / ambiguous
// 3. BestMatch → return method; Ambiguous → luaL_error; None → luaL_error
}
Error message prefix zlua:, aligned with Mono goals (spec §7).
5. Cooperation with MethodMarshalCtx
Each candidate overload already has its own MethodMarshalCtx at bind time (paramsMeta[], retMeta, lua2CsInvoker stub).
Resolver only selects ctx; it does not re-marshal. After selection:
MethodBridge::InvokeLua2Cs(L, target, argStart, ctx);
Virtual calls: MetadataUtil::ResolveInvokeMethod(ctx->method, target, ctx->sealed) inside InvokeLua2Cs.
6. Constructor overload
Type SMT.__call uses the same MethodGroups / Resolver mechanism; ctx comes from .ctor overload set (TypeBinding::ctorGroups); argStart skips type-table this (static construction has no this).
Mono Phase 3: ConstructorEmitter replaced ConstructorNotReady; currently first-wins by arity; full better-member Resolver still pending.
7. Full-signature keys, aliases, register_method, and Resolver
Spec:
| Source | Name-collision policy |
|---|---|
Bind-time default name + [LuaAlias] | Allowed; aggregate into MethodGroups by final name |
| Multiple bind-time candidates with same name | Default name → dispatch; each candidate also gets full-signature key Name(Types…) → direct |
Runtime register_method | Forbidden to occupy existing method / overload / full-signature key names; only empty slots get direct (short name + colon) |
| Mechanism | Implementation landing |
|---|---|
| Single candidate → direct closure | MetaBinding::CreateDirectMethodClosureRef |
| Multi candidate → dispatch | CreateMethodDispatchClosureRef → Resolver |
| Full-signature keys | Bind-time write one direct key per candidate |
[LuaAlias] | Bind-time merge into MethodGroups for that final name |
register_method | Name exists → error; else write method map / index table (direct closure) |
Alias keys are O(1) direct only when that final name has exactly one candidate; bind-time collisions go through Resolver. Full-signature keys are always direct for their candidate.
8. Known limits (code status)
| Item | Status |
|---|---|
byref parameters | FIXME: handle byref in GetConversionKind; currently only lightuserdata Identity |
| Static generic methods | InvokeMethodDirectGeneric → explicit error |
[LuaMarshalAs(ParamsTable)] | (Removed; params is single-slot same as szarray) |
When implementing or fixing, update spec tests and Mono Emit in sync.
9. Mono alignment plan
Phase 3 Emit will generate for multi-overload methods:
- dispatch Lua/C# closure (captures
MethodGroups-equivalent structure); - managed
MethodOverloadResolver.Resolve(mirrorConversionKindenum and compare rules); - Jump to that overload's dedicated Emit bridge (not
Method.Invoke).
Acceptance: same Lua call selects the same overload as Il2Cpp (including tie-break).
10. Related files
| File | Responsibility |
|---|---|
marshal/MethodOverloadResolver.cpp/.h | Resolve, GetConversionKind |
marshal/MarshalDefs.h | MethodGroups, ConversionKind |
mt/MetaBinding.cpp | Build groups, dispatch closure |
bridge/MethodBridge.cpp | InvokeLua2Cs |
Editor/CppCodeGen/AotMethodAnalyzer.cs | Build-time overload metadata order |
Spec details: ../../spec/04-METHOD-OVERLOAD.md