@@ -429,58 +429,134 @@ inline std::optional<int> try_call_newindex_extensible(lua_State* L, const char*
429429 LUABRIDGE_ASSERT (key != nullptr );
430430 LUABRIDGE_ASSERT (lua_istable (L, -1 )); // Stack: mt
431431
432+ // For Lua function values (instance methods added from Lua), capture the originating class
433+ // table so the method is stored there rather than in a traversed-to parent class table.
434+ // This prevents e.g. `function Derived:init()` from polluting `Base`'s method table.
435+ //
436+ // For non-function values (static properties like `Class.prop = val`), the original
437+ // traversal-end behaviour is preserved: storing them in the nearest class table that
438+ // already has a matching key (or the topmost base if none exists). This keeps static
439+ // properties out of the derived class table, which is also used for instance extensible
440+ // method lookup, avoiding unintended shadowing of per-instance properties accessed via an
441+ // index-fallback metamethod registered on a non-extensible base class.
442+ const bool value_is_function = lua_isfunction (L, 3 );
443+
444+ if (value_is_function)
445+ {
446+ // Capture the original (most-derived) class table — always write here.
447+ push_class_or_const_table (L, -1 ); // Stack: mt, orig_ct | nil
448+ if (! lua_istable (L, -1 )) // Stack: mt, nil
449+ {
450+ lua_pop (L, 1 ); // Stack: mt
451+ return std::nullopt ;
452+ }
453+ // Stack: mt, orig_ct
454+
455+ lua_pushvalue (L, -2 ); // Stack: mt, orig_ct, cur_mt (traversal copy)
456+
457+ for (;;)
458+ {
459+ push_class_or_const_table (L, -1 ); // Stack: mt, orig_ct, cur_mt, cur_ct | nil
460+ if (! lua_istable (L, -1 )) // Stack: mt, orig_ct, cur_mt, nil
461+ {
462+ lua_pop (L, 2 ); // Stack: mt, orig_ct
463+ break ;
464+ }
465+
466+ lua_pushvalue (L, 2 ); // Stack: mt, orig_ct, cur_mt, cur_ct, field name
467+ lua_rawget (L, -2 ); // Stack: mt, orig_ct, cur_mt, cur_ct, field | nil
468+
469+ if (! lua_isnil (L, -1 )) // Stack: mt, orig_ct, cur_mt, cur_ct, field
470+ {
471+ if (! lua_iscfunction (L, -1 ))
472+ {
473+ lua_pop (L, 3 ); // Stack: mt, orig_ct
474+ break ;
475+ }
476+
477+ const Options options = get_class_options (L, -2 ); // Stack: mt, orig_ct, cur_mt, cur_ct, field
478+ if (! options.test (allowOverridingMethods))
479+ luaL_error (L, " immutable member '%s'" , key);
480+
481+ // Store super_ alias in the ORIGINAL (derived) class table so only derived
482+ // instances can call it; the base class table is left completely untouched.
483+ rawsetfield (L, -4 , make_super_method_name (key).c_str ()); // Stack: mt, orig_ct, cur_mt, cur_ct
484+ lua_pop (L, 2 ); // Stack: mt, orig_ct
485+ break ;
486+ }
487+
488+ lua_pop (L, 1 ); // Stack: mt, orig_ct, cur_mt, cur_ct
489+
490+ lua_rawgetp_x (L, -2 , getParentKey ()); // Stack: mt, orig_ct, cur_mt, cur_ct, pmt | nil
491+ if (lua_isnil (L, -1 )) // Stack: mt, orig_ct, cur_mt, cur_ct, nil
492+ {
493+ lua_pop (L, 3 ); // Stack: mt, orig_ct
494+ break ;
495+ }
496+
497+ LUABRIDGE_ASSERT (lua_istable (L, -1 )); // Stack: mt, orig_ct, cur_mt, cur_ct, pmt
498+ lua_remove (L, -2 ); // Stack: mt, orig_ct, cur_mt, pmt
499+ lua_remove (L, -2 ); // Stack: mt, orig_ct, pmt
500+ }
501+
502+ // Stack: mt, orig_ct — write to the original (most-derived) class table.
503+ lua_getmetatable (L, -1 ); // Stack: mt, orig_ct, orig_ct_mt
504+ lua_pushvalue (L, 3 ); // Stack: mt, orig_ct, orig_ct_mt, arg3
505+ rawsetfield (L, -2 , key); // Stack: mt, orig_ct, orig_ct_mt
506+ lua_pop (L, 2 ); // Stack: mt
507+ return 0 ;
508+ }
509+
510+ // Non-function value (static property): use original traversal-end write location.
432511 lua_pushvalue (L, -1 ); // Stack: mt, mt
433512
434513 for (;;)
435514 {
436- push_class_or_const_table (L, -1 ); // Stack: mt, mt, class table (ct) | nil
515+ push_class_or_const_table (L, -1 ); // Stack: mt, mt, ct | nil
437516 if (! lua_istable (L, -1 )) // Stack: mt, mt, nil
438517 {
439518 lua_pop (L, 2 ); // Stack: mt
440519 return std::nullopt ;
441520 }
442521
443- lua_pushvalue (L, 2 ); // Stack: mt, mt, ct | co , field name
444- lua_rawget (L, -2 ); // Stack: mt, mt, ct | co , field | nil
522+ lua_pushvalue (L, 2 ); // Stack: mt, mt, ct, field name
523+ lua_rawget (L, -2 ); // Stack: mt, mt, ct, field | nil
445524
446- if (! lua_isnil (L, -1 )) // Stack: mt, mt, ct | co , field
525+ if (! lua_isnil (L, -1 )) // Stack: mt, mt, ct, field
447526 {
448527 if (! lua_iscfunction (L, -1 ))
449528 {
450529 lua_pop (L, 1 );
451530 break ;
452531 }
453532
454- // Obtain class options
455- const Options options = get_class_options (L, -2 ); // Stack: mt, mt, ct | co, field
533+ const Options options = get_class_options (L, -2 ); // Stack: mt, mt, ct, field
456534 if (! options.test (allowOverridingMethods))
457535 luaL_error (L, " immutable member '%s'" , key);
458536
459- rawsetfield (L, -2 , make_super_method_name (key).c_str ()); // Stack: mt, mt, ct | co
537+ rawsetfield (L, -2 , make_super_method_name (key).c_str ()); // Stack: mt, mt, ct
460538 break ;
461539 }
462540
463- lua_pop (L, 1 ); // Stack: mt, mt, ct | co
541+ lua_pop (L, 1 ); // Stack: mt, mt, ct
464542
465- lua_rawgetp_x (L, -2 , getParentKey ()); // Stack: mt, mt, ct | co, parent mt ( pmt) | nil
466- if (lua_isnil (L, -1 )) // Stack: mt, mt, ct | co , nil
543+ lua_rawgetp_x (L, -2 , getParentKey ()); // Stack: mt, mt, ct, pmt | nil
544+ if (lua_isnil (L, -1 )) // Stack: mt, mt, ct, nil
467545 {
468- lua_pop (L, 1 ); // Stack: mt, mt, ct | co
546+ lua_pop (L, 1 ); // Stack: mt, mt, ct
469547 break ;
470548 }
471549
472- LUABRIDGE_ASSERT (lua_istable (L, -1 )); // Stack: mt, mt, ct | co , pmt
550+ LUABRIDGE_ASSERT (lua_istable (L, -1 )); // Stack: mt, mt, ct, pmt
473551 lua_remove (L, -2 ); // Stack: mt, mt, pmt
474552 lua_remove (L, -2 ); // Stack: mt, pmt
475553 }
476554
477- lua_remove (L, -2 ); // Stack: mt, ct | co
478- lua_getmetatable (L, -1 ); // Stack: mt, ct | co, mt2
479- lua_pushvalue (L, 3 ); // Stack: mt, ct | co, mt2, arg3
480- rawsetfield (L, -2 , key); // Stack: mt, ct | co, mt2
481-
555+ lua_remove (L, -2 ); // Stack: mt, ct
556+ lua_getmetatable (L, -1 ); // Stack: mt, ct, ct_mt
557+ lua_pushvalue (L, 3 ); // Stack: mt, ct, ct_mt, arg3
558+ rawsetfield (L, -2 , key); // Stack: mt, ct, ct_mt
482559 lua_pop (L, 2 ); // Stack: mt
483-
484560 return 0 ;
485561}
486562
0 commit comments