Skip to content

A partial Derma skin is rejected: SKIN requires all 100 members, but partial skins are how the system works #9

Description

@AmyJeanes

Summary

Passing a partial Derma skin to derma.DefineSkin reports 100 missing required fields. Partial skins are valid and idiomatic — the engine resolves anything a skin omits from the Default skin.

There is no Structures/SKIN wiki page (it 404s), so this is entirely custom/class.SKIN.lua and not wiki-fixable.

Why partial skins are correct

derma.DefineSkin gives every non-Default skin a metatable that falls back to the Default skin:

SkinMetaTable.__index = function( self, key )
    return DefaultSkin[ key ]
end
...
function DefineSkin( strName, strDescription, strTable )
    strTable.Base = strTable.Base or "Default"
    if ( strName != "Default" ) then
        setmetatable( strTable, SkinMetaTable )

(garrysmod/lua/derma/derma.lua:20-24, 130-140.) If a skin genuinely had to supply all 100 members, that fallback would be unreachable code.

Where the 100 comes from — two separate causes

custom/class.SKIN.lua does two things to the same global:

  1. ---@class SKIN declares 49 fields, of which only 3 are optional (Name?, Description?, Base? — exactly the three DefineSkin assigns itself).
  2. From SKIN = SKIN or {} onward it implements the Default skin on that same global — 54 SKIN.<field> = ... assignments (PrintName, Author, GwenTexture, the colour and font tables).

Both fold into the class.

Marking the 49 declared fields optional would not fix this. Assignment-derived members are demanded just as strongly. Minimal proof, no GMod annotations involved:

-- lib/decl.lua
---@meta
---@class Thing
---@field declared_req string
---@field declared_opt? string
Thing = Thing or {}
Thing.assigned_field = "x"
Thing.assigned_two = 5

-- use.lua
---@param t Thing
local function take(t) return t end
take({ declared_req = "y" })
warning: Missing required fields in type `Thing`: `assigned_field`, `assigned_two` [missing-fields]

The declared-optional field is correctly ignored; both assigned fields are reported missing.

Repro

Repro zip: repro.zip

derma.DefineSkin("repro_skin", "A partial skin", {
    PaintButton = function(self, panel, w, h) end,
})
warning: Missing required fields in type `SKIN`: `Author`, `Colours`, `DermaVersion`, ... (100 fields) ... `tooltip` [missing-fields]
warning: expected `SKIN` but found `{ PaintButton = ... }`. missing member Colours, in table [param-type-mismatch]

It also fires inconsistently

Whether a partial skin is rejected depends only on how it reaches DefineSkin, not on its contents:

SKIN = {}
SKIN.PrintName = "Test"
derma.DefineSkin("a", "via the global", SKIN)          -- clean

derma.DefineSkin("b", "via a literal", { PrintName = "Test" })   -- 100 missing fields

Identical content, opposite verdicts. The check only engages on a table literal at the call site — which is also why this isn't more widely reported, since the conventional skin file assigns to the global SKIN and happens to dodge it.

Suggested fix

Both causes need addressing, and the second is a design call I didn't want to guess at:

  1. Mark the declared SKIN fields optional. None is mandatory — all are inherited-or-overridable.
  2. Keep the Default skin's implementation off the SKIN class. Declaring the interface and then assigning a concrete default implementation onto the same global is what contributes the other 54.

Happy to send a PR for whichever shape you'd prefer for (2) — I held off precisely because it changes what custom/class.SKIN.lua is doing.

Real-world impact

Ecosystem-level rather than a live cost to me — but not because my skin is complete. It defines 51 of the 100 members and none of the Paint* methods; it is thoroughly partial and works correctly, because everything it omits resolves from the Default skin at runtime. It simply routes through the global, so the diagnostic never fires.

Any addon that passes a partial skin as a literal hits it immediately, and the only workarounds are a missing-fields suppression or writing out all 100 members.

Environment

  • beta @ 07d30c6; annotations gluals-annotations-prerelease @ 2c8a727e
  • glua_check / glua_ls 1.1.1, Windows 11

Disclosure: this report was researched and written by Claude Code working in my repositories. Every claim in it was reproduced and verified against real workspaces rather than asserted, and the two of us went through the findings together before I filed it. Happy to run further tests or narrow anything down if that would help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions