Skip to main content

MethodOverloadResolver Implementation

Il2Cpp: marshal/MethodOverloadResolver.cpp Mono: Phase 3 Emit/ + 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

  1. Lua calls a dispatch closure (not a direct method closure).
  2. Dispatch closure upvalue carries MethodGroups* (MetaBinding::CreateMethodDispatchClosureRef).
  3. MethodOverloadResolver::Resolve(L, groups, argStart, argCount) returns a unique MethodMarshalCtx* or errors.
  4. 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* from MarshalMetaInfo;
  • 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:

SourceName-collision policy
Bind-time default name + [LuaAlias]Allowed; aggregate into MethodGroups by final name
Multiple bind-time candidates with same nameDefault name → dispatch; each candidate also gets full-signature key Name(Types…) → direct
Runtime register_methodForbidden to occupy existing method / overload / full-signature key names; only empty slots get direct (short name + colon)
MechanismImplementation landing
Single candidate → direct closureMetaBinding::CreateDirectMethodClosureRef
Multi candidate → dispatchCreateMethodDispatchClosureRef → Resolver
Full-signature keysBind-time write one direct key per candidate
[LuaAlias]Bind-time merge into MethodGroups for that final name
register_methodName 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)

ItemStatus
byref parametersFIXME: handle byref in GetConversionKind; currently only lightuserdata Identity
Static generic methodsInvokeMethodDirectGeneric → 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:

  1. dispatch Lua/C# closure (captures MethodGroups-equivalent structure);
  2. managed MethodOverloadResolver.Resolve (mirror ConversionKind enum and compare rules);
  3. Jump to that overload's dedicated Emit bridge (not Method.Invoke).

Acceptance: same Lua call selects the same overload as Il2Cpp (including tie-break).

FileResponsibility
marshal/MethodOverloadResolver.cpp/.hResolve, GetConversionKind
marshal/MarshalDefs.hMethodGroups, ConversionKind
mt/MetaBinding.cppBuild groups, dispatch closure
bridge/MethodBridge.cppInvokeLua2Cs
Editor/CppCodeGen/AotMethodAnalyzer.csBuild-time overload metadata order

Spec details: ../../spec/04-METHOD-OVERLOAD.md