Refactor visibility handling: centralize visibility constants and improve error messaging for access violations
This commit is contained in:
parent
c7237452df
commit
1a492d9b9a
43
oop/oop.lua
43
oop/oop.lua
@ -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()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user