dev

This module was made as a sandbox for the user Thundercraft5. This documentation is kept to prevent redlinks.

Output

574

88

198

538

811

640

248

686

463

561

37

35

26

851

688

479

628

244


local p = {}
local helpers = {}
local util = {}
local classMtFuncs = {}
local staticMtFuncs = {}
local protoMtFuncs = {}
local classCount = 0

_G._DEBUG = _G.DEBUG or false
local _DEBUG = _G._DEBUG
local getmetatable, setmetatable, string, table, select, error, ipairs, pairs, tostring, type, rawget, rawset, next, unpack = getmetatable, setmetatable, string, table, select, error, ipairs, pairs, tostring, type, rawget, rawset, next, unpack
local tinsert = table.insert
local sformat = string.format
local debug = debug

local MESSAGES = {
	INVALID_SUPER_CALL = "Invalid super constructor call. The class must have a parent to call the super constructor",
	GETTER_ONLY_ASSIGNMENT = 'Cannot set class property %q{property} which has only a getter',
	GETTER_ONLY_STATIC_ASSIGNMENT = 'Cannot set static class property %q{property} which has only a getter',
	STATIC_GETTER_ONLY_ASSIGNMENT = 'Cannot set static class property %q{property} which has only a getter',
	MUST_CALL_SUPER = "Invalid class construction. The child class constructor must call `self:super(...)`",
	INVALID_CLASS_ACCESSOR = "Invalid class %{accessor} %q{property}: class %{accessor}s must be functions, recived a %{type} (%q{value}) instead",
	INVALID_STATIC_CLASS_ACCESSOR = "Invalid static class %{accessor} %q{property}: class %{accessor}s must be functions, recived a %{type} (%q{value}) instead",
	INVAID_SELF_SUPER = ":super() was passed an invalid self object of type %{type} (%q{value}). Did you call :super() with a '.' instead of a ':', i.e `.super(...)` instead of `:super(...)`",
	CLASS_FIELD_RESERVED = "Cannot set class property %q{property} because it is reserved",
	STATIC_CLASS_FIELD_RESERVED = "Cannot set static class property %q{property} because it is reserved",
	PROTO_FIELD_RESERVED = "Cannot set class prototype property %q{property} because it is reserved",
	PROPERTIES_TYPE = 'Class properties must be of type table, recived type %q{type} instead',
	STATIC_PROPERTIES_TYPE = 'Class static properties must be of type table, recived type %q{type} instead',
	INVALID_PARENT_CLASS = "Invalid parent class. The parent class must be a class created by makeClass()",
	VALUE_NOT_CLASS = "bad argument #%{pos} to %q{name} (value of type %{type} (%q{value}) is not a class)",
	VALUE_NOT_INSTANCE = "bad argument #%{pos} to %q{name} (value of type %{type} (%q{value}) is not a class instance)",
	VALUE_NOT_CLASS_OR_INSTANCE = "bad argument #%{pos} to %q{name} (value of type %{type} (%q{value}) is not a class instance or class)",
	VALUE_NOT_CLASS_OR_INSTANCE_OR_PROTO = "Value of type %{type} (%q{value}) is not a class instance or class or a class prototype object",
	INVALID_SELF_OBJECT = "Class method :%{name}() was passed an invalid self object of type %{type} (%q{value}). Did you mean to use ':' instead of '.' to call this method (`:%{name}(...)` instead of `.%{name}(...)`)",
	INVALID_STATIC_SELF_OBJECT = "Static class method :%{name}() was passed an invalid self object of type %{type} (%q{value}). Did you mean to use ':' instead of '.' to call this method (`:%{name}(...)` instead of `.%{name}(...)`)",
	INVALID_PROTO_VALUE = "Cannot reassign the class prototype to a value whose type is not a table, recived a value of type %{type} (%q{value}) instead",
	INVALID_METHOD_ARG_TYPE = "bad argument #%{pos} to class method %q{name} (%{expected} expected, got %{type})",
	VALUE_EXPECTED = "bad argument #%{pos} to class method %q{name} (value expected)",
	INVALID_STATIC_METHOD_ARG_TYPE = "bad argument #%{pos} to static class method %q{name} (%{expected} expected, got %{type})",
	VALUE_EXPECTED_STATIC = "bad argument #%{pos} to static class method %q{name} (value expected)",
	INVAID_ARG_TYPE = "bad argument #%{pos} to %q{name} (%{expected} expected, got %{type})",
}

-- Global list of classes
local classRegistry = setmetatable({}, { __mode="k" })
local instanceRegistry = setmetatable({}, { __mode="k" })
local prototypeRegistry = setmetatable({}, { __mode="k" })
---------------------------------------------------------------------------------
-- Helper functions
---------------------------------------------------------------------------------
-- Pack values
function helpers.pack(...)
	local n = select("#", ...)

	return { n = n, ... };
end

local function noop() end

function helpers.safetostring(v)
	local _, res = xpcall(function()
		return tostring(v)
	end, function()
		if p.isClass(v) then
			return "class"
		elseif p.isInstance(v) then
			return "class instance"
		elseif p.isPrototype(v) then
			return "class prototype"
		else
			return type(v)
		end
	end)

	return res
end

-- interpolate a formatted string, syntax is the same as `string.format(...)` except items are annotated with `%<format options>{<tableKey>}`
-- ex: helpers.interpolate("%q{test}", { test="Test1" }) -> "\"Test1\""
function helpers.interpolate(s, substutions)
	local items = {}
	local i = 0

	s = s:gsub('(%%([%d%.%#+%-]*)(%w?)%b{})', function(w, options, substutionType)
		i = i + 1
		local start, stop = 3 + (#(options .. substutionType)), -2
		tinsert(items, substutions[w:sub(start, stop)] or error("Missing interpolation parameter '" .. w:sub(start, stop) .. "'", 4))

		return "%" .. (substutionType ~= "" and substutionType or "s")
	end)

	return sformat(s, unpack(items, 1, i))
end

-- Bind a `self` value to a function
-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
if _DEBUG then
	function helpers.proxy(t, func)
		return function(...)
			local res = helpers.pack(({ ["<class internals>"] = func })["<class internals>"](t, ...))

			if t then return unpack(res, 1, res.n) end
		end
	end
else
	function helpers.proxy(t, func)
		return function(...)
			return func(t, ...)
		end
	end
end

-- Get table keys
function helpers.tableKeys(t)
	local ret = {}

	for k in pairs(t) do
		tinsert(ret, k)
	end

	return ret
end

-- Merge keys into table
function helpers.merge(t1, ...)
	if select('#', ...) > 1 then
		for _, t in ipairs{ ... } do
			for k, v in pairs(t) do
				t1[k] = v
			end
		end
	else
		for k, v in pairs(...) do
			t1[k] = v
		end
	end

	return t1
end

function helpers.clearTable(t)
	for k in pairs(t) do
		t[k] = nil
	end

	return t
end

function helpers.getMethodName(level)
	local methodName = mw.text.split(debug.traceback(), "\n")[(level or 0) + 3]:match("in function [\'\"](.-)[\'\"]")

	return methodName
end

function helpers.getName(value)
	local s

	if p.isInstance(value) then s = 'instance of class "' .. debug.getNameOf(value) .. '"'
	elseif p.isPrototype(value) then s = 'prototype of class "' .. debug.getNameOf(value) .. '"'
	elseif p.isClass(value) then s = 'class "' .. debug.getNameOf(value) .. '"' 
	else s = type(value) .. ' ("' .. helpers.safetostring(value) .. '")'
	end
	
	
	return s
end

-- Function to create a child class form a given parent
function helpers.createChildClass(parent, classData)
	helpers.assertStaticSelf(parent)
	classData = classData or {}
	
	if type(classData) == 'string' then
		return function(data)
			data = data or {}
			
			data.parent = parent
			data.__metadata = data.__metadata or {}
			data.__metadata.name = classData
			
			return p.makeClass(data)
		end
	else
		classData.parent = parent
		return p.makeClass(classData)
	end
end
---------------------------------------------------------------------------------
-- Assertion utilities
---------------------------------------------------------------------------------
function helpers.assertIsClass(v, pos)
	if not p.isClass(v) then error(helpers.interpolate(MESSAGES.VALUE_NOT_CLASS, { value = v ~= nil and helpers.safetostring(v) or "nil", type = type(v), pos = pos or 1, name = helpers.getMethodName(1) }), 3) end

	return v
end

function helpers.assertIsInstance(v, pos)
	if not p.isInstance(v) then error(helpers.interpolate(MESSAGES.VALUE_NOT_INSTANCE, { value = v ~= nil and helpers.safetostring(v) or "nil", type = type(v), pos = pos or 1, name = helpers.getMethodName(1) }), 3) end

	return v
end

function helpers.assertIsInstanceOrClass(v, pos)
	if not p.isInstance(v) and not p.isClass(v) then error(helpers.interpolate(MESSAGES.VALUE_NOT_CLASS_OR_INSTANCE, { value = v ~= nil and helpers.safetostring(v) or "nil", type = type(v), pos = pos or 1, name = helpers.getMethodName(1) }), 3) end

	return v
end

function helpers.assertIsInstanceOrClassOrProto(v, pos)
	if not p.isInstance(v) and not p.isClass(v) and not p.isPrototype(v) then error(helpers.interpolate(MESSAGES.VALUE_NOT_CLASS_OR_INSTANCE_OR_PROTO, { value = v ~= nil and helpers.safetostring(v) or "nil", type = type(v), pos = pos or 1, name = helpers.getMethodName(1) }), 3) end

	return v
end

function helpers.assertStaticSelf(v)
	if not p.isClass(v) then error(helpers.interpolate(MESSAGES.INVALID_STATIC_SELF_OBJECT, { name = helpers.getMethodName(1), value = helpers.safetostring(v), type = type(v) }), 3) end

	return v
end

---------------------------------------------------------------------------------
-- functions used inside of interals
---------------------------------------------------------------------------------
function helpers.defaultConstructor(self)
	return self
end

function helpers.defaultInheritedConstructor(self, ...)
	self:super(...)

	return self
end

function helpers.invalidSuperCall()
	error(MESSAGES.INVALID_SUPER_CALL, 2)
end

function helpers.ipairsFunc(t, i)
	i = i + 1

	local v = t[i]
	if v ~= nil then return i, v end
end

---------------------------------------------------------------------------------
-- Overwrite mw.dumpObject to accept classes
---------------------------------------------------------------------------------
function mw.dumpObject(object)
	local doneTable = {}
	local doneObj = {}
	local ct = {}
	local function sorter(a, b)
		local ta, tb = type(a), type(b)
		if ta ~= tb then
			return ta < tb
		end
		if ta == 'string' or ta == 'number' then
			return a < b
		end
		if ta == 'boolean' then
			return tostring(a) < tostring(b)
		end
		return false -- Incomparable
	end
	local function _dumpObject(object, indent, expandTable)
		local tp = type(object)
		if tp == 'number' or tp == 'nil' or tp == 'boolean' then
			return tostring(object)
		elseif tp == 'string' then
			return string.format("%q", object)
		elseif tp == 'table' then
			local s = helpers.safetostring(object)

			if not doneObj[object] then
				if s == 'table' or classRegistry[object] or instanceRegistry[object] or prototypeRegistry[object] then
					if classRegistry[object] then s = 'class(' .. object.__metadata__.name .. ')'
					elseif instanceRegistry[object] then s = 'class instance('  .. object.__class__.__metadata__.name .. ')'
					elseif prototypeRegistry[object] then s = 'class prototype(' .. object.__holdingClass__.__metadata__.name .. ')'
					end

					ct[tp] = (ct[tp] or 0) + 1
					doneObj[object] = s .. '#' .. ct[tp]
				else
					doneObj[object] = s
					doneTable[object] = true
				end
			end
			if doneTable[object] or not expandTable then
				return doneObj[object]
			end
			doneTable[object] = true

			local ret = { doneObj[object], ' {\n' }
			local mt = getmetatable(object)
			if mt then
				ret[#ret + 1] = string.rep(" ", indent + 2)
				ret[#ret + 1] = 'metatable = '
				ret[#ret + 1] = _dumpObject(mt, indent + 2, false)
				ret[#ret + 1] = "\n"
			end

			local doneKeys = {}
			for key, value in ipairs(object) do
				doneKeys[key] = true
				ret[#ret + 1] = string.rep(" ", indent + 2)
				ret[#ret + 1] = _dumpObject(value, indent + 2, true)
				ret[#ret + 1] = ',\n'
			end
			local keys = {}
			for key in pairs(object) do
				if not doneKeys[key] then
					keys[#keys + 1] = key
				end
			end
			table.sort(keys, sorter)
			for i = 1, #keys do
				local key = keys[i]
				ret[#ret + 1] = string.rep(" ", indent + 2)
				ret[#ret + 1] = '['
				ret[#ret + 1] = _dumpObject(key, indent + 3, false)
				ret[#ret + 1] = '] = '
				ret[#ret + 1] = _dumpObject(object[key], indent + 2, true)
				ret[#ret + 1] = ",\n"
			end

			if p.isClass(object) or p.isPrototype(object) or p.isInstance(object) then
				local proto = object.__proto__

				if proto then
					ret[#ret + 1] = string.rep(" ", indent + 2)
					ret[#ret + 1] = '(Prototype) = '
					ret[#ret + 1] = _dumpObject(object.__proto__, indent + 2, true)
					ret[#ret + 1] = ",\n"
				end
			end

			ret[#ret + 1] = string.rep(" ", indent)
			ret[#ret + 1] = '}'

			return table.concat(ret)
		else
			if not doneObj[object] then
				ct[tp] = (ct[tp] or 0) + 1
				doneObj[object] = tostring(object) .. '#' .. ct[tp]
			end
			return doneObj[object]
		end
	end
	return _dumpObject(object, 0, true)
end

function mw.logObject(v, prefix)
	mw.log((prefix and prefix .. " " or "") .. mw.dumpObject(v))
end

---------------------------------------------------------------------------------
-- Internal Data
---------------------------------------------------------------------------------
local metaProperties = {
	['__index'] = 1,
	['__newindex'] = 1,
	['__mode'] = 1,
	['__tostring'] = 1,
	['__concat'] = 1,
	['__metatable'] = 1,
	['__ipairs'] = 1,
	['__pairs'] = 1,
	['__pow'] = 1,
	['__add'] = 1,
	['__sub'] = 1,
	['__div'] = 1,
	['__mul'] = 1,
	['__unm'] = 1,
	['__eq'] = 1,
	['__lt'] = 1,
	['__le'] = 1,
}

-- TODO: Maybe add custom metamethods?
local customStaticMetaProperties = {
	['__getter'] = 1,
	['__setter'] = 1,
}

local customMetaProperties = {
	['__getter'] = 1,
	['__setter'] = 1,
	['__protoIndex'] = 1,
}

-- Properties that maybe added directly to the metatable without need for further modificiation
local contextSafeProperties = {
	['__tostring'] = 1,
	['__pairs'] = 1,
	['__ipairs'] = 1,
	["__unm"] = 1,
}

-- Relational operators
local relationalOperators = {
	['__add'] = 1,
	['__sub'] = 1,
	['__div'] = 1,
	['__mul'] = 1,
	['__pow'] = 1,
	['__le'] = 1,
	['__lt'] = 1,
	['__concat'] = 1,
}

local reservedProps = {
	["super"] = 1,
	["__proto__"] = 1,
	['__getters__'] = 1,
	['__setters__'] = 1,
	['__parent__'] = 1,
	['__class__'] = 1,
	['__static__'] = 1,
	['__metadata__'] = 1,
}

local reservedProtoProps = {
	['__proto__'] = 1,
	['__holdingClass__'] = 1,
	['__getters__'] = 1,
	['__setters__'] = 1,
	['__parent__'] = 1,
}

local reservedStaticProps = {
	['__parent__'] = 1,
	['__proto__'] = 1,
	['__setters__'] = 1,
	['__getters__'] = 1,
	['__children__'] = 1,
	['__parents__'] = 1,
	['__metadata__'] = 1,
	['constructor'] = 1,
	['super'] = 1,
	['childClass'] = 1,
}

---------------------------------------------------------------------------------
-- Class structure
--
-- Static class
--  * Static metatable
--	* Static Parent metatable
--  * Class prototype
--	* Class prototype metatable
-- * Class instance
--  * Class instance metatable
-- * Parent class (repeat above)
---------------------------------------------------------------------------------
function helpers:overwriteProto(v, isOverwriting)
	if isOverwriting and #helpers.tableKeys(self.__prototype) > 0 then
		helpers.clearTable(self.__prototype)
		helpers.clearTable(self.__classGetters)
		helpers.clearTable(self.__classSetters)
		helpers.clearTable(self.__classMetamethods)
	end

	for k, v in pairs(v) do
		self.__protoMt.__fakeTable[k] = v
	end
end

function helpers.parseAccessorTable(k, v, setters, getters, len, static)
	static = static ~= false

	len = len or #helpers.tableKeys(v)
	local get = v.get
	local set = v.set
	if len > 0 and len <= 2 and (set or get) then
		if set ~= nil then
			if type(set) == "function" then
				setters[k] = set
			else
				return helpers.interpolate(
					static and MESSAGES.INVALID_STATIC_CLASS_ACCESSOR or MESSAGES.INVALID_CLASS_ACCESSOR,
					{ accessor = "setter", property = k, type = type(set), value = set }
				)
			end
		end
		if get ~= nil then
			if type(get) == "function" then
				getters[k] = get
			else
				return helpers.interpolate(
					static and MESSAGES.INVALID_STATIC_CLASS_ACCESSOR or MESSAGES.INVALID_CLASS_ACCESSOR,
					{ accessor = "getter", property = k, type = type(get), value = get }
				)
			end
		end
	end
end

---------------------------------------------------------------------------------
-- Class metatable methods
---------------------------------------------------------------------------------
function classMtFuncs.__index(t, k)
	local self = instanceRegistry[t]
	local staticMt = self.__staticMt

	if k == 'super' then return self.__super end		
	if reservedProps[k] then
		if k == '__getters__' then return staticMt.__classGetters
		elseif k == '__setters__' then return staticMt.__classSetters
		elseif k == '__static__'
		or k == '__class__' then return staticMt.__class 
		elseif k == '__proto__' then return staticMt.__protoMt.__fakeTable
		elseif k == '__parent__' then return staticMt.__parentStaticMt.__class
		end
	end
	
	local __index = staticMt.__classMetamethods.__index
	local protoProp = staticMt.__prototype[k]
	local getter = staticMt.__classGetters[k]

	-- If requested value does not exist in current prototype, repeat same step on parent prototypes
	if staticMt.__hasParent and protoProp == nil and getter == nil then
		local cur = staticMt.__parentStaticMt
		local curGetters = cur.__classGetters
		local curProto = cur.__prototype

		while cur do
			local value = curProto[k]
			local foundGetter = curGetters[k]

			if foundGetter ~= nil then
				getter = foundGetter
				break
			elseif value ~= nil then
				protoProp = value
				break
			end

			if not cur.__parentStaticMt then
				break
			end

			cur = cur.__parentStaticMt
			curProto = cur.__prototype
			curGetters = cur.__classGetters
		end
	end

	if getter ~= nil then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(({ ["<class getter>"] = getter })["<class getter>"](t))

			if getter then return unpack(res, 1, res.n) end
		else
			return getter(t)
		end
	end

	if protoProp ~= nil then
		return protoProp
	else
		if __index then
			-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stack
			if _DEBUG then
				local res = helpers.pack(__index(t, k))
	
				if self then return unpack(res, 1, res.n) end
			else
				return __index(t, k)
			end
		end

		return nil
	end
end

function classMtFuncs.__newindex(t, k, v)
	if reservedProps[k] then error(helpers.interpolate(MESSAGES.CLASS_FIELD_RESERVED, { property = k }), 2) end

	local self = instanceRegistry[t]
	local staticMt = self.__staticMt
	local setter = staticMt.__classSetters[k]
	local getter = staticMt.__classGetters[k]
	local __newindex = staticMt.__classMetamethods.__newindex 

	-- Search for setter on parent classes
	if staticMt.__hasParent and setter == nil then
		local cur = staticMt.__parentStaticMt
		local curSetters = cur.__classSetters
		local curGetters = cur.__classGetters
		local curProto = cur.__prototype

		while cur do
			local foundSetter = curSetters[k]
			getter = curGetters[k]

			if foundSetter ~= nil then
				setter = foundSetter
				break
			end

			curProto = cur.__prototype
			curSetters = cur.__classSetters
			cur = cur.__parentStaticMt
		end
	end
	
	if getter and not setter then
		error(helpers.interpolate(MESSAGES.GETTER_ONLY_ASSIGNMENT, { property = k }), 3)
	elseif setter ~= nil then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(({ ["<class setter>"] = setter })["<class setter>"](t, v))

			if setter then return unpack(res, 1, res.n) end
		else
			return setter(t, v)
		end
	else
		if __newindex then
			-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
			if _DEBUG then
				local res = helpers.pack(__newindex(t, k, v))
	
				if self then return unpack(res, 1, res.n) end
			else
				return __newindex(t, k, v)
			end
		end

		return rawset(t, k, v)
	end
end

-- Override default pairs(), invoke class getters in the process
function classMtFuncs.__pairs(t)
	local self = instanceRegistry[t]
	local staticMt = self.__staticMt
	local getters = helpers.tableKeys(staticMt.__classGetters)
	local i = 0
	local onGetters = false
	local __pairs = staticMt.__classMetamethods.__pairs

	if __pairs then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(__pairs(t, k, v))

			if self then return unpack(res, 1, res.n) end
		else
			return __pairs(t, k, v)
		end
	end

	return function(t, k1)
		local k, v

		if not onGetters then
			k, v = next(t, k1)
		end

		-- Insert getters into results
		if k == nil then
			onGetters = true
			i = i + 1

			k, v = getters[i], t[getters[i]]
		end

		if k == nil then
			return nil, nil
		else
			return k, v
		end
	end, t, nil
end

function classMtFuncs.__ipairs(t)
	local self = instanceRegistry[t]
	local __ipairs = self.__staticMt.__classMetamethods.__ipairs

	if __ipairs then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(__ipairs(t, k, v))

			if self then return unpack(res, 1, res.n) end
		else
			return __ipairs(t, k, v)
		end
	end

	return function(t, i)
		i = i + 1

		local v = t[i]
		if v ~= nil then return i, v end
	end, t, 0
end


function classMtFuncs.__tostring(t)
	local self = instanceRegistry[t]
	local __tostring = self.__staticMt.__classMetamethods.__tostring

	if __tostring then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(__tostring(t, k, v))

			if self then return unpack(res, 1, res.n) end
		else
			return __tostring(t, k, v)
		end
	end


	return "class instance"
end

---------------------------------------------------------------------------------
-- Static class metamethods
---------------------------------------------------------------------------------
-- Construct class metadatatable
function helpers.createClassMt(staticMt)
	local classMt = {
		__staticMt = staticMt,
	}

	classMt.__metatable = {}

	-- Set metamethods
	for k, v in pairs(classMtFuncs) do
		classMt[k] = v
		classMt.__metatable[k] = v
	end

	return classMt
end

-- Called when actually constructing the class
function staticMtFuncs.__call(t, ...)
	local created = {}
	local self = classRegistry[t] or (instanceRegistry[t] and instanceRegistry[t].__staticMt) or error(helpers.interpolate(MESSAGES.INVALID_SELF_OBJECT, { name = helpers.getMethodName(), value = helpers.safetostring(t), type = type(t) }), 2)
	local classMt = helpers.createClassMt(self)
	local superCalled = false
	local done = false

	setmetatable(created, classMt)

	instanceRegistry[created] = classMt

	-- Set class properties
	for k, v in pairs(self.__initProps) do
		rawset(created, k, v)
	end

	-- set class metamethods
	for k, v in pairs(self.__classMetamethods) do
		if contextSafeProperties[k] or k == '__eq' or k == '__metatable' then
			classMt[k] = v
		elseif relationalOperators[k] then
			classMt[k] = v
		end
	end

	local i = 0
	local len = #self.__parents

	classMt.__constructorArgs = helpers.pack(...)

	-- TODO: Add super indexing support
	if self.__parentStaticMt then
		local curParent = self.__parents[1]

		local function superFunc(...)
			i = i + 1
			local passedSelf = ({ ... })[1]
			if not instanceRegistry[passedSelf] then error(MESSAGES.INVAID_SELF_SUPER, 2) end

			curParent = classRegistry[self.__parents[i]] or error(MESSAGES.INVALID_SUPER_CALL, 2)
			local constructor = curParent.__origConstructor
			
			local res = helpers.pack(constructor(...));
			
			if (res.n == 0 or res.n == 1) and res[1] ~= passedSelf then
				return passedSelf
			elseif res.n > 1 then
				if res[1] == nil then res[1] = passedSelf end
				
				return unpack(res, 1, res.n)
			else
				return passedSelf
			end
		end

		-- .super() is returned via `__index`
		classMt.__super = superFunc
	else
		classMt.__super = helpers.invalidSuperCall
	end
	local constructor = self.__origConstructor
	local ret = helpers.pack(constructor(created, ...))

	if self.__parentStaticMt and i ~= len then
		error(MESSAGES.MUST_CALL_SUPER, 3);
	end

	if (ret.n == 0 or ret.n == 1) and ret[1] ~= created then
		return created
	elseif ret.n > 1 then
		if ret[1] ~= created then ret[1] = created end
		
		return unpack(ret, 1, ret.n)
	else
		return created
	end
end

function staticMtFuncs.__newindex(t, k, v)
	local self = classRegistry[t]
	if reservedStaticProps[k] then error(helpers.interpolate(MESSAGES.STATIC_CLASS_FIELD_RESERVED, { property = k }), 2) end

	-- If key is to Overwrite the prototype, clear any residual prototype keys and reset from new table
	if k == 'prototype' then
		if type(v) == 'table' then
			helpers.overwriteProto(self, v, true)

			return self.__protoMt.__fakeTable
		else
			error(helpers.interpolate(MESSAGES.INVALID_PROTO_VALUE, { type = type(v), value = helpers.safetostring(v) }), 2)
		end
	end

	local setter = self.__staticSetters[k]
	local __newindex = self.__staticMetamethods.__newindex

	-- Search for setter in parent classes
	if self.__hasParent and setter == nil then
		local cur = self.__parentStaticMt
		local curSetters = cur.__staticSetters
		local curGetters = cur.__staticGetters
		local curProto = cur.__prototype

		while cur do
			local foundSetter = curSetters[k]
			local foundGetter = curGetters[k]

			if foundSetter ~= nil or foundGetter ~= nil then
				setter = foundSetter
				getter = foundGetter
				break
			end

			cur = cur.__parentStaticMt

			if not cur then break end
			curProto = cur.__prototype
			curSetters = cur.__staticSetters
		end
	end

	-- If getter but not setter exists raise exception
	if getter and not setter then
		error(helpers.interpolate(MESSAGES.GETTER_ONLY_STATIC_ASSIGNMENT, { property = k }) , 3)
	elseif setter ~= nil then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(({ ["<static class setter>"] = setter })["<static class setter>"](t, v))
	
			if setter then return unpack(res, 1, res.n) end
		else
			return setter(t, v)
		end
	else
		if __newindex then
			-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
			if _DEBUG then
				local res = helpers.pack(__newindex(t))	
				
				if self then return unpack(res, 1, res.n) end
			else
				return __newindex(t)
			end
		end

		return rawset(t, k, v)
	end
end

function staticMtFuncs.__index(t, k)
	if util[k] then return util[k] end

	local self = classRegistry[t]

	if self.__indexDefaults[k] then return self.__indexDefaults[k] end

	local getter = self.__staticGetters[k]
	local value = rawget(t, k)
	local __index = self.__staticMetamethods.__index

	-- Search for getter/prototype property on parents
	if self.__hasParent and getter == nil and value == nil then
		local cur = self.__parentStaticMt
		local curGetters = cur.__staticGetters
		local curProto = cur.__prototype

		while cur do
			local foundGetter = curGetters[k]
			local foundValue = rawget(cur.__class, k)
			
			if foundValue ~= nil then
				value = foundValue
				break
			elseif foundGetter ~= nil then
				getter = foundGetter
				break
			end

			cur = cur.__parentStaticMt

			if not cur then break end
			curProto = cur.__prototype
			curGetters = cur.__staticGetters
		end
	end

	if getter ~= nil then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(({ ["<static class getter>"] = getter })["<static class getter>"](t))
	
			if getter then return unpack(res, 1, res.n) end
		else
			return getter(t)
		end
	else
		if __index then
			-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
			if _DEBUG then
				local res = helpers.pack(__index(t))	
				
				if self then return unpack(res, 1, res.n) end
			else
				return __index(t)
			end
		end

		return value
	end
end

function staticMtFuncs.__pairs(t)
	local self = classRegistry[t]
	local __pairs = self.__staticMetamethods.__pairs

	if __pairs then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(__pairs(t))	
			
			if self then return unpack(res, 1, res.n) end
		else
			return __pairs(t)
		end
	end

	local getters = helpers.tableKeys(self.__staticGetters)
	local i = 0
	local onGetters = false
	local protoDone = false

	return function(t, k1)
		local k, v

		-- Insert prototype into results
		if k1 == 'prototype' and not protoDone then
			protoDone = true
			return 'prototype', self.__protoMt.__fakeTable
		end

		if not onGetters then
			k, v = next(t, k1 ~= 'prototype' and k1 or nil)
		end

		-- Insert getters into results
		if k == nil then
			onGetters = true
			i = i + 1

			k, v = getters[i], t[getters[i]]
		end

		if k == nil then
			return nil, nil
		else
			return k, v
		end
	end, t, 'prototype'
end


function staticMtFuncs.__ipairs(t)
	local self = classRegistry[t]
	local __ipairs = self.__staticMetamethods.__ipairs

	if __ipairs then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(__ipairs(t))	
			
			if self then return unpack(res, 1, res.n) end
		else
			return __ipairs(t)
		end
	end

	return helpers.ipairsFunc, t, 0
end

function staticMtFuncs.__tostring(t)
	local self = classRegistry[t]
	local __tostring = self.__staticMetamethods.__tostring

	if __tostring then
		-- If in Debug mode, forbid tail call optimizations as that will obfuscate the function name in error stacks
		if _DEBUG then
			local res = helpers.pack(__tostring(t))	
			
			if self then return unpack(res, 1, res.n) end
		else
			return __tostring(t)
		end
	end

	return "class"
end

---------------------------------------------------------------------------------
-- Prototype metamethods
---------------------------------------------------------------------------------
function protoMtFuncs.__pairs(t)
	local self = prototypeRegistry[t]

	return function(t, k)
		local nextKey, value = next(self.__prototype, k);

		return nextKey, value
	end, self.__fakeTable, nil
end

function protoMtFuncs.__ipairs(t)
	return helpers.ipairsFunc, prototypeRegistry[t], 0
end

function protoMtFuncs.__newindex(t, k, v)
	local self = prototypeRegistry[t]
	local tp = type(v)

	if reservedProtoProps[k] then error(helpers.interpolate(MESSAGES.PROTO_FIELD_RESERVED, { property = k })) end
	if k == 'constructor' then
		-- Overwrite constructor
		local oldConstructor = self.__prototype.constructor
		local newConstructor = v

		self.__prototype.constructor = staticMtFuncs.__call
		self.__origConstructor = newConstructor or oldConstructor

		return self.__fakeTable
	elseif tp == 'table' and next(v) ~= nil and k ~= '__metatable' then
		local err = helpers.parseAccessorTable(k, v, self.__staticMt.__classSetters, self.__staticMt.__classGetters, nil, false)

		if err then error(err, 2) end
		return
	else
		if metaProperties[k] then
			self.__staticMt.__classMetamethods[k] = v
		end
	end
	self.__prototype[k] = v
	
	return self.__fakeTable
end

function protoMtFuncs.__index(t, k)
	local self = prototypeRegistry[t]
	if self.__indexDefaults[k] then return self.__indexDefaults[k] end

	local value = self.__prototype[k]
	
	if value == nil then
		local parent = self.__parentMt
	
		while parent do
			local foundValue = parent.__prototype[k]

			if foundValue ~= nil then
				value = foundValue
				break 
			end
						
			parent = parent.__parentMt
		end
	end
	
	return value
end

function protoMtFuncs.__tostring()
	return "class prototype"
end

---------------------------------------------------------------------------------
-- Utilies, exposed on the static class
---------------------------------------------------------------------------------
function util:checkSelf(value, name)
	if not classRegistry[self] then error(helpers.interpolate(MESSAGES.INVALID_STATIC_SELF_OBJECT, { name = helpers.getMethodName(-1), value = helpers.safetostring(self), type = type(self) }), 2) end 

	if not self:instanceof(value) then
		local methodName = name or helpers.getMethodName(1)

		error(helpers.interpolate(MESSAGES.INVALID_SELF_OBJECT, { name = methodName, value = helpers.safetostring(value), type = type(value) }), 3)
	end

	return self
end

function util:checkSelfStatic(value, name)
	if not classRegistry[self] then error(helpers.interpolate(MESSAGES.INVALID_STATIC_SELF_OBJECT, { name = helpers.getMethodName(-1), value = helpers.safetostring(self), type = type(self) }), 2) end

	if not self:isChildOrEqual(value) then
		local methodName = name or mw.text.split(debug.traceback(), "\n")[3]:match("in function [\'\"](.-)[\'\"]")

		error(helpers.interpolate(MESSAGES.INVALID_STATIC_SELF_OBJECT, { name = methodName, value = helpers.safetostring(value), type = type(value) }), 3)
	end

	return self
end

function util:isChildOrEqual(value)
	if not classRegistry[self] then error(helpers.interpolate(MESSAGES.INVALID_STATIC_SELF_OBJECT, { name = helpers.getMethodName(-1), value = helpers.safetostring(self), type = type(self) }), 2) end

	local cr = classRegistry[value]
	if not cr then return false end
	if value == self then return true end	
	
	local i = 1
	local parents = cr.__parents
	local class = parents[i]

	while class do
		if class == self then return true end

		i = i + 1
		class = parents[i]
	end
	
	return false
end

-- Check if value is an instance of class
function util:instanceof(value)
	if not classRegistry[self] then error(helpers.interpolate(MESSAGES.INVALID_STATIC_SELF_OBJECT, { name = helpers.getMethodName(-1), value = helpers.safetostring(self), type = type(self) }), 2) end
	if not instanceRegistry[value] then return false end

	local class = value.__class__
	local parents = classRegistry[class].__parents

	if class == self then return true end

	local i = 1
	local parent = parents[i]

	while parent do
		if parent == self then return true end

		i = i + 1
		parent = parents[i]
	end

	return false
end

-- Check types of method arguments
function util:checkTypes(types, ...)
	if not classRegistry[self] then error(helpers.interpolate(MESSAGES.INVALID_STATIC_SELF_OBJECT, { name = helpers.getMethodName(-1), value = helpers.safetostring(self), type = type(self) }), 2) end

	local max = #types
	local values = helpers.pack(...)
	local i = 1
	local value = values[1]
	-- Rename error function to get proper name in stack
	local checkTypes = error

	while i <= max do
		local valueType = type(value)
		local targetType = types[i]

		if i > values.n then 
			checkTypes(helpers.interpolate(MESSAGES.INVALID_METHOD_ARG_TYPE, { name = helpers.getMethodName(1), type = "no value", pos = i, expected = helpers.getName(targetType) }), 3)
		end

		if p.isClass(targetType) then
			if not targetType:instanceof(value) then
				checkTypes(helpers.interpolate(MESSAGES.INVALID_METHOD_ARG_TYPE, { 
					name = helpers.getMethodName(1), 
					pos = i, 
					value = helpers.safetostring(value), 
					type = valueType, 
					expected = helpers.getName(targetType) 
				}), 3)
			end
		else
			if valueType ~= targetType then
				checkTypes(helpers.interpolate(MESSAGES.INVALID_METHOD_ARG_TYPE, { 
					name = helpers.getMethodName(1),
					pos = i,
					value = helpers.safetostring(value),
					type = helpers.getName(value),
					expected = targetType 
				}), 3)
			end
		end

		i = i + 1
		value = values[i]
	end

	return ...
end

---------------------------------------------------------------------------------
-- Package exports
---------------------------------------------------------------------------------
function p.isClass(v)
	return classRegistry[v] ~= nil
end

function p.isInstance(v)
	return instanceRegistry[v] ~= nil
end

function p.isPrototype(v)
	return prototypeRegistry[v] ~= nil
end

function p.makeClass(...)
	local args = { ... }
	local name, data

	if type(args[1]) == 'string' then
		data = args[2]
		name = args[1]
	else
		data = args[1]
	end

	data = data or {}

	if type(data) ~= 'table' then error(string.format('Argument #1 must be a table or a string (class name) or nil, recived %q instead', type(data)), 2) end
	if type(data) ~= 'table' then error(string.format('Argument #2 must be a table or nil, recived %q instead', type(data)), 2) end

	local parentClass = data.parentClass or data.parent
	local constructor = data.constructor or (parentClass and helpers.defaultInheritedConstructor or helpers.defaultConstructor)
	local staticData = data.static or {}
	local metadata = data.__metadata or {}
	local classFields = data.class or {}

	if type(staticData) ~= 'table' then error(helpers.interpolate(MESSAGES.STATIC_PROPERTIES_TYPE, { type = type(staticData) }), 2) end
	if type(classFields) ~= 'table' then error(helpers.interpolate(MESSAGES.PROPERTIES_TYPE, { type = type(classFields) }), 2) end

	local staticMt = {}

	local Class = {}
	local prototype = data.prototype or {}
	local initProps = {}

	local setters, getters = {}, {}

	classCount = classCount + 1

	if not classRegistry[parentClass] and parentClass ~= nil then
		error(MESSAGES.INVALID_PARENT_CLASS, 2)
	end

	-- Setup metadata
	metadata.name = name or metadata.name or "unnamed class " .. classCount
	metadata.id = classCount

	---------------------------------------------------------------------------------
	-- Set up static metatable
	---------------------------------------------------------------------------------
	helpers.merge(staticMt, {
		__prototype = prototype,
		__class = Class,
		__initProps = initProps,
		__origConstructor = constructor,

		__classSetters = setters,
		__classGetters = getters,
		__staticSetters = {},
		__staticGetters = {},
		__staticMetamethods = {},

		__parentClass = parentClass,
		__parentStaticMt = parentClass and classRegistry[parentClass],

		__isInstance = false,
		__hasParent = not not parentClass,
		__classMetamethods = {},
		__parents = setmetatable({}, { __mode = "v" }),
		__childClasses = setmetatable({}, { __mode = "v" }),
	});

	helpers.merge(staticMt, staticMtFuncs)

	---------------------------------------------------------------------------------
	-- Set static/class methods
	---------------------------------------------------------------------------------
	for k, v in pairs(data) do
		if k ~= 'constructor'
		and k ~= 'parent'
		and k ~= 'class'
		and k ~= 'prototype'
		and k ~= '__metadata'
		and k ~= 'static' then
			local tp = type(v)
			if tp == 'function' then
				rawset(prototype, k, v)
				if metaProperties[k] then
					staticMt.__classMetamethods[k] = v
				end
			elseif tp == 'table' then
				local len = #helpers.tableKeys(v)

				if (len <= 2 and len > 0) and (v.set or v.get) then
					local err = helpers.parseAccessorTable(k, v, staticMt.__classSetters, staticMt.__classGetters, len, false)
					if err then error(err, 2) end
				else
					rawset(initProps, k, v)
				end
			else
				rawset(initProps, k, v)
			end
		end
	end

	if next(classFields) ~= nil then
		for k, v in pairs(classFields) do
			initProps[k] = v
		end
	end

	if next(staticData) ~= nil then
		for k, v in pairs(staticData) do
			if reservedStaticProps[k] then error(helpers.interpolate(MESSAGES.STATIC_CLASS_FIELD_RESERVED, { property = k }), 2) end
			local tp = type(v)

			if tp == 'table' then
				local len = #helpers.tableKeys(v)
				if (len > 0 and len <= 2) and (v.get or v.set) then
					local err = helpers.parseAccessorTable(k, v, staticMt.__staticSetters, staticMt.__staticGetters, len, true)
					if err then error(err, 2) end
				else
					rawset(Class, k, v)
				end
			else
				rawset(Class, k, v)
			end

			if metaProperties[k] and tp == 'function' then
				if contextSafeProperties[k] or relationalOperators[k] or k == '__eq' or k == '__metatable' then
					staticMt[k] = v
				end

				staticMt.__staticMetamethods[k] = v
			-- TODO: Add metatable property verification
			-- elseif metaProperties[k] then
			-- 	error("Metatable properties must be functions.")
			end
		end
	end

	---------------------------------------------------------------------------------
	-- Set up prototype metatable
	---------------------------------------------------------------------------------
	local protoMt = {}
	local fakeProto = {}

	helpers.merge(protoMt, {
		__staticMt = staticMt,
		__class = Class,
		__prototype = staticMt.__prototype,
		__parentMt = staticMt.__hasParent and staticMt.__parentStaticMt.__protoMt or nil,
		__fakeTable = fakeProto,
		__indexDefaults = setmetatable({
			['__setters__'] = staticMt.__classSetters,
			['__getters__'] = staticMt.__classGetters,
			['__proto__'] = staticMt.__hasParent and staticMt.__parentStaticMt.__protoMt.__fakeTable or nil,
			['__holdingClass__'] = staticMt.__class,
			['__parent__'] = staticMt.__hasParent and staticMt.__parentStaticMt.__class,
		}, { __mode = "v" }),
	});

	protoMt.__prototype.constructor = staticMtFuncs.__call
	staticMt.__protoMt = protoMt
	staticMt.__metatable = {
		__isClass = true,
		__isInstance = false,
		__hasParent = staticMt.__hasParent,
		__class = Class,
		__parent = staticMt.__parentClass,
	}
	helpers.overwriteProto(staticMt, prototype, false)

	---------------------------------------------------------------------------------
	-- Set up final metadata for static metatable
	---------------------------------------------------------------------------------
	staticMt.__customMetadata = metadata
	staticMt.__indexDefaults = setmetatable({
		['__parent__'] = staticMt.__parentClass,
		['__proto__'] = staticMt.__parentClass,
		['prototype'] = protoMt.__fakeTable,
		['__setters__'] = staticMt.__staticSetters,
		['__getters__'] = staticMt.__staticGetters,
		['__children__'] = staticMt.__childClasses,
		['__parents__'] = staticMt.__parents,
		['__metadata__'] = staticMt.__customMetadata,
		['constructor'] = staticMt.__prototype.constructor,
		['childClass'] = helpers.createChildClass,
	}, { __mode = "v" })

	-- Set prototype table metamethods
	for k, v in pairs(protoMtFuncs) do
		protoMt[k] = v
	end
	prototypeRegistry[fakeProto] = protoMt

	-- Merge parent initProps into target class and add child class to parent class list
	if parentClass then
		local parentMt = staticMt.__parentStaticMt

		table.insert(classRegistry[parentClass].__childClasses, Class)

		while parentMt do
			tinsert(staticMt.__parents, parentMt.__class)
			helpers.merge(initProps, staticMt.__initProps)

			parentMt = parentMt.__parentStaticMt
		end
	end

	-- Finalize setup
	setmetatable(fakeProto, protoMt)

	classRegistry[Class] = staticMt

	return setmetatable(Class, staticMt)
end
---------------------------------------------------------------------------------
-- Debug functions (Only use for debugging!)
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
-- Class instance debug functions
---------------------------------------------------------------------------------
function debug.instanceExists(value)
	return not not instanceRegistry[helpers.assertIsInstance(value)]
end

function debug.getInstanceMetatable(instance)
	return instanceRegistry[helpers.assertIsInstance(instance)]
end

function debug.hasInstanceMetamethod(instance, method)
	return not not debug.getInstanceMetamethod(helpers.assertIsInstance(instance), method)
end

function debug.getInstanceMetamethod(instance, method)
	return debug.getInstanceMetatable(helpers.assertIsInstance(instance)).__staticMt.__classMetamethods[method]
end

function debug.getInstanceSetters(instance)
	return helpers.assertIsInstance(instance).__setters__
end

function debug.getInstanceGetters(instance)
	return helpers.assertIsInstance(instance).__getters__
end

---------------------------------------------------------------------------------
-- Class debug functions
---------------------------------------------------------------------------------
function debug.classExists(value)
	return not not classRegistry[helpers.assertIsClass(value)]
end

function debug.getClassMetatable(class)
	return classRegistry[helpers.assertIsClass(class)]
end

function debug.hasClassMetamethod(class, method)
	return not not debug.getClassMetamethod(helpers.assertIsClass(class), method)
end

function debug.getClassMetamethod(class, method)
	return debug.getClassMetatable(helpers.assertIsClass(class)).__staticMetamethods[method]
end

function debug.getClassSetters(class)
	return helpers.assertIsClass(class).__setters__
end

function debug.getClassGetters(class)
	return helpers.assertIsClass(class).__getters__
end

function debug.getClassParents(value)
	return helpers.assertIsClass(value).__parent__
end

function debug.getClassName(class)
	return classRegistry[helpers.assertIsClass(class)].__customMetadata.name
end

function debug.getClassId(class)
	return classRegistry[helpers.assertIsClass(class)].__customMetadata.id
end

function debug.getClassByName(name)
	if type(name) ~= 'string' then error("Argument #1 must be a string", 2) end

	for k, v in pairs(classRegistry) do
		if v.__customMetadata.name == name then return k end	
	end
	
	return nil
end

---------------------------------------------------------------------------------
-- Class prototype functions
---------------------------------------------------------------------------------
function debug.getClassOfPrototype(proto)
	return helpers.assertIsPrototype(proto).__holdingClass__
end

function debug.getPrototypeMetatable(proto)
	return prototypeRegistry[helpers.assertIsPrototype(proto)]
end

function debug.getPrototypeTable(proto)
	return prototypeRegistry[helpers.assertIsPrototype(proto)].__prototype
end

function debug.getPrototypeConstructor(proto)
	return prototypeRegistry[helpers.assertIsPrototype(proto)].__prototype.constructor
end

---------------------------------------------------------------------------------
-- Class/Class instance functions
---------------------------------------------------------------------------------
function debug.getParentOf(value)
	return helpers.assertIsInstanceOrClassOrProto(value).__parent__
end

function debug.getPrototypeOf(value)
	return helpers.assertIsInstanceOrClassOrProto(value).__proto__
end

function debug.getNameOf(value)
	helpers.assertIsInstanceOrClassOrProto(value)
	
	if p.isClass(value) then
		return classRegistry[value].__customMetadata.name
	elseif p.isPrototype(value) then
		return prototypeRegistry[value].__staticMt.__customMetadata.name
	elseif p.isInstance(value) then
		return instanceRegistry[value].__staticMt.__customMetadata.name
	end
	
	error("Invalid program state entered")
end

function p.test()
	local start = os.clock()
	local Parent = p.makeClass("Parent", {
		constructor = function(self)
			helpers.assertIsInstance(self)
		end,
		static = {
			test = {
				set = function(self, v)
					error(v, 0)
					self._test = v
				end;
				get = function(self)
					return 0
				end;
			},
		}
	})
	local Class = p.makeClass("Class", {
		parent = Parent,
		constructor = function(self)
			self:super()
			-- self[1] = 1
			-- mw.logObject(self)
			-- self.test = 0
			
			return {}, 0
		end,

		{
			get = function()
				return 8
			end,
		},
		0,

		[0] = {
			set = function(self, v)
				self._test = v;
			end;
			get = function(self)
				return self._test or 6;
			end;
		},

		_test = 6;
		__unm = function(self)
			return self.test
		end,
		__add = function(self, a, b)
			return a.test + b
		end,
		__eq = function(a, b)
			return a.test == b.test
		end,
		__concat = function(self, a, b)
			return assert(a == self)
		end,
		test2 = 0,
		class = {
			test0 = 5,
		},
		__newindex = function(self, k, v)
			-- error(k)
		end,
		__index = function(self, k)
			-- error(k)
		end,
		static = {
			0,
			0,
			_test = 0,
			isData = error,
			__concat = function(self, a, b)
				return tostring(a) .. tostring(b)
			end,
			test = function(self)
				Parent:checkSelfStatic(self)
			end,
		},
	});

	Parent.prototype = {
		test7 = function(self, ...)
			Class:checkSelf(self)
				:checkTypes({ Parent, "string" }, ...)
		end,
		test2 = {
			set = function(self, v)
				return error(v)
			end,
		}
	};
	mw.log("Tests: " .. os.clock() - start)
	start = os.clock()

	local t = {}
	local Test = Class:childClass{
		constructor = function(self, ...)
			return self:super()
		end
	}
	local c = Parent()
	for i = 1, 10000 do
		local c = Test()
	end
	mw.log("Creating 10k classes: " .. os.clock() - start)

	return mw.dumpObject(#helpers.tableKeys(instanceRegistry))
end

return p