Refactor visibility handling: centralize visibility constants and improve error messaging for access violations

This commit is contained in:
Chipperfluff 2026-01-01 17:47:57 +01:00
parent c7237452df
commit 1a492d9b9a

View File

@ -1,5 +1,11 @@
local oop = {} local oop = {}
oop.Visibility = {
PUBLIC = "public",
PROTECTED = "protected",
PRIVATE = "private"
}
local tpack = table.pack or function(...) local tpack = table.pack or function(...)
return { n = select("#", ...), ... } return { n = select("#", ...), ... }
end end
@ -30,12 +36,12 @@ oop._call_stack = {}
local function normalize_visibility(visibility) local function normalize_visibility(visibility)
if visibility == nil then if visibility == nil then
return "public" return oop.Visibility.PUBLIC
end end
if visibility == "public" or visibility == "protected" or visibility == "private" then if visibility == oop.Visibility.PUBLIC or visibility == oop.Visibility.PROTECTED or visibility == oop.Visibility.PRIVATE then
return visibility return visibility
end end
error("visibility must be 'public', 'protected', or 'private'") error("visibility must be Visibility.PUBLIC, Visibility.PROTECTED, or Visibility.PRIVATE")
end end
local function is_class_table(target) local function is_class_table(target)
@ -58,21 +64,36 @@ local function current_caller()
end end
local function can_access(owner, visibility, caller) local function can_access(owner, visibility, caller)
if visibility == "public" then if visibility == oop.Visibility.PUBLIC then
return true return true
end end
if not caller or not owner then if not caller or not owner then
return false return false
end end
if visibility == "private" then if visibility == oop.Visibility.PRIVATE then
return caller == owner return caller == owner
end end
if visibility == "protected" then if visibility == oop.Visibility.PROTECTED then
return oop.issubclass(caller, owner) return oop.issubclass(caller, owner)
end end
return false return false
end end
local function class_name(cls)
if type(cls) ~= "table" then
return tostring(cls)
end
return rawget(cls, "__name") or "Anonymous"
end
local function visibility_error(kind, member, visibility, owner, caller)
local from = caller and class_name(caller) or "outside"
local where = owner and class_name(owner) or "unknown"
local msg = "access violation: " .. kind .. " '" .. tostring(member) .. "' is " .. visibility
.. " in " .. where .. " (from " .. from .. ")"
error(msg, 3)
end
function oop.setattr(target, key, value, visibility) function oop.setattr(target, key, value, visibility)
target[key] = value target[key] = value
if is_class_table(target) then if is_class_table(target) then
@ -135,7 +156,7 @@ function lookup_visibility(cls, key)
end end
cur = rawget(cur, "__base") cur = rawget(cur, "__base")
end end
return "public", nil return oop.Visibility.PUBLIC, nil
end end
function oop.class(def, base) function oop.class(def, base)
@ -143,6 +164,7 @@ function oop.class(def, base)
cls.__name = "Anonymous" cls.__name = "Anonymous"
cls.__base = base cls.__base = base
cls.__visibility = {} cls.__visibility = {}
cls.visibility = oop.Visibility
cls.__index = function(self, key) cls.__index = function(self, key)
local fields = rawget(self, "__fields") local fields = rawget(self, "__fields")
if fields then if fields then
@ -150,7 +172,7 @@ function oop.class(def, base)
if field_val ~= nil then if field_val ~= nil then
local vis, owner = lookup_visibility(cls, key) local vis, owner = lookup_visibility(cls, key)
if not can_access(owner, vis, current_caller()) then if not can_access(owner, vis, current_caller()) then
error("attempt to access " .. vis .. " member '" .. tostring(key) .. "'") visibility_error("field", key, vis, owner, current_caller())
end end
return field_val return field_val
end end
@ -163,7 +185,7 @@ function oop.class(def, base)
local vis, vis_owner = lookup_visibility(cls, key) local vis, vis_owner = lookup_visibility(cls, key)
local access_owner = vis_owner or owner local access_owner = vis_owner or owner
if not can_access(access_owner, vis, current_caller()) then if not can_access(access_owner, vis, current_caller()) then
error("attempt to access " .. vis .. " member '" .. tostring(key) .. "'") visibility_error("member", key, vis, access_owner, current_caller())
end end
if type(val) == "function" then if type(val) == "function" then
return function(...) return function(...)
@ -179,7 +201,7 @@ function oop.class(def, base)
end end
local vis, owner = lookup_visibility(cls, key) local vis, owner = lookup_visibility(cls, key)
if owner and not can_access(owner, vis, current_caller()) then if owner and not can_access(owner, vis, current_caller()) then
error("attempt to set " .. vis .. " member '" .. tostring(key) .. "'") visibility_error("field", key, vis, owner, current_caller())
end end
local fields = rawget(self, "__fields") local fields = rawget(self, "__fields")
if not fields then if not fields then
@ -284,6 +306,7 @@ function oop.install(env)
target.setattr = oop.setattr target.setattr = oop.setattr
target.isinstance = oop.isinstance target.isinstance = oop.isinstance
target.issubclass = oop.issubclass target.issubclass = oop.issubclass
target.Visibility = oop.Visibility
end end
oop.install() oop.install()