Skip to content

refactor: collapse repeated argument guards into CheckArgs - #9

Merged
mschmicking merged 1 commit into
masterfrom
refactor-check-args
Aug 8, 2026
Merged

refactor: collapse repeated argument guards into CheckArgs#9
mschmicking merged 1 commit into
masterfrom
refactor-check-args

Conversation

@mschmicking

Copy link
Copy Markdown
Owner

Found while auditing master for structure. 217 of 673 lines in luastate.cc were argument guards.

The problem

Every method opened with the same preamble — arity check, a type check per argument, then EnsureOpen — each throwing and returning by hand. SetGlobal was typical: 22 lines, of which 2 did the work.

Napi::Value LuaState::SetGlobal(const Napi::CallbackInfo& info) {
	Napi::Env env = info.Env();

	if(info.Length() < 1){
		Napi::TypeError::New(env, "LuaState.SetGlobal Requires 1 Argument").ThrowAsJavaScriptException();
		return env.Undefined();
	}

	if(!info[0].IsString()){
		Napi::TypeError::New(env, "LuaState.SetGlobal Argument 1 Must Be A String").ThrowAsJavaScriptException();
		return env.Undefined();
	}

	if(!EnsureOpen(env)){
		return env.Undefined();
	}

	std::string global_name = info[0].As<Napi::String>().Utf8Value();
	lua_setglobal(lua_, global_name.c_str());

	return env.Undefined();
}

becomes

Napi::Value LuaState::SetGlobal(const Napi::CallbackInfo& info) {
	Napi::Env env = info.Env();
	if(!CheckArgs(info, "SetGlobal", {Arg::String}) || !EnsureOpen(env)){
		return env.Undefined();
	}

	std::string global_name = info[0].As<Napi::String>().Utf8Value();
	lua_setglobal(lua_, global_name.c_str());

	return env.Undefined();
}

What was added

  • CheckArgs(info, method, {Arg::String, ...}) — validates arity and types, throws in the established wording.
  • ThrowLuaError(env, L, prefix) — builds the message, pops the error, throws. The pop was previously written out at each of the six call sites and is easy to omit, which silently grows the Lua stack. Replaces lua_error_message, whose callers all had to remember to pop.
  • EnsureIndexable — the non-table check that was duplicated between SetField and GetField.

Pop and SetTop keep their hand-written checks: their argument is optional, so CheckArgs does not apply. The comments now say so, rather than leaving it looking like an oversight.

Behaviour is unchanged, and that was checked rather than assumed

Messages are now generated, so I verified all fourteen distinct forms are byte-identical to the hardcoded originals — including CollectGarbage's , try nodelua.GC.[TYPE] suffix and the singular/plural split:

"LuaState.DoString Requires 1 Argument"
"LuaState.Call Requires 2 Arguments"
"LuaState.SetField Requires 3 Arguments"
"LuaState.CollectGarbage Argument 1 Must Be A Number, try nodelua.GC.[TYPE]"
"LuaState.RegisterFunction Argument 2 Must Be A Function"
"LuaState.GetField: Value At The Given Index Is Not A Table"
"LuaState Has Already Been Closed"
...

62/62 tests pass, all three examples run.

Result

Before After
luastate.cc 672 447
guard boilerplate 217 63
src/ total 899 747

🤖 Generated with Claude Code

@mschmicking
mschmicking force-pushed the refactor-check-args branch 2 times, most recently from 57f2c34 to 19da4ee Compare August 8, 2026 08:26
@mschmicking
mschmicking force-pushed the refactor-check-args branch from 19da4ee to c2b1495 Compare August 8, 2026 08:51
Every method opened with the same 15-line preamble: an arity check, a type
check per argument, then an EnsureOpen check, each throwing and returning by
hand. That was 217 of 673 lines in luastate.cc, and SetGlobal was typical --
22 lines of which 2 did the work.

Adds CheckArgs(info, method, {Arg::String, ...}), which validates arity and
types and throws in the established wording, so a method now opens with one
line. The messages are generated rather than hardcoded, and were verified
byte-identical for all fourteen distinct forms, including CollectGarbage's
', try nodelua.GC.[TYPE]' hint and the singular/plural split between
'Requires 1 Argument' and 'Requires 2 Arguments'.

Also adds ThrowLuaError, which builds the message, pops the error and throws.
Popping was previously written out at each of the six call sites and is easy to
omit, which silently grows the stack. Replaces lua_error_message, whose callers
all had to remember to pop afterwards.

Extracts EnsureIndexable, the non-table check duplicated between SetField and
GetField.

Pop and SetTop keep their hand-written checks: their argument is optional, so
CheckArgs does not apply, and the comments now say so.

luastate.cc 672 -> 447 lines, guard boilerplate 217 -> 63, src/ down 152 lines
overall. No behaviour change: 62/62 tests pass and all three examples run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mschmicking
mschmicking force-pushed the refactor-check-args branch from c2b1495 to c2cca33 Compare August 8, 2026 08:57
@mschmicking
mschmicking merged commit 4def99b into master Aug 8, 2026
13 checks passed
@mschmicking
mschmicking deleted the refactor-check-args branch August 8, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant