From d7d2f5167e7c267dc9932d5c815536bf247bd5e9 Mon Sep 17 00:00:00 2001 From: centauri <83140718+centau@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:35:34 +0000 Subject: [PATCH] Use type functions for `vide.create` --- src/create.luau | 158 ++++++++++++++++++++++------------------- test/create-types.luau | 2 +- 2 files changed, 87 insertions(+), 73 deletions(-) diff --git a/src/create.luau b/src/create.luau index 193aa0f..6273b80 100644 --- a/src/create.luau +++ b/src/create.luau @@ -5,90 +5,104 @@ local defaults = require "./defaults" local apply = require "./apply" local flags = require "./flags" -local ctor_cache = {} :: { [string]: () -> Instance } +local ctor_cache = {} :: { [string]: (AnyProps) -> Instance } +local function lazy_init(_, class: string) + local function ctor(properties: AnyProps): Instance + local ok, instance: Instance = pcall(Instance.new, class :: any) + if not ok then error(`invalid class name { class }`, 0) end -setmetatable(ctor_cache :: any, { - __index = function(self, class) - local function ctor(properties: Props): Instance - local ok, instance: Instance = pcall(Instance.new, class :: any) - if not ok then error(`invalid class name { class }`, 0) end - - if flags.defaults then - local default: { [string]: unknown }? = defaults[class] - if default then - for i, v in default do - (instance :: any)[i] = v - end + if flags.defaults then + local default: { [string]: unknown }? = defaults[class] + if default then + for i, v in default do + (instance :: any)[i] = v end end - - return apply(instance, properties) end - self[class] = ctor - return ctor + return apply(instance, properties) end -}) -local function create_instance(class: string) - return ctor_cache[class] + ctor_cache[class] = ctor + return ctor end +setmetatable(ctor_cache, { __index = lazy_init }) -local function clone_instance(instance: Instance) - return function(properties: Props): Instance - local clone = instance:Clone() - if not clone then error "attempt to clone a non-archivable instance" end - return apply(clone, properties) +-- todo: remove support for different overloads +local function create(class_or_instance: string|Instance, properties: AnyProps?): ((AnyProps) -> Instance) | Instance + if type(class_or_instance) ~= "string" and typeof(class_or_instance) ~= "Instance" then + error("bad argument #1, expected string or instance, got " .. typeof(class_or_instance), 0) end + + local ctor = if type(class_or_instance) == "string" + then ctor_cache[class_or_instance] + else function(properties) + local clone = class_or_instance:Clone() + if not clone then error "attempt to clone a non-archivable instance" end + return apply(clone, properties) + end + + return if properties + then ctor(properties) + else ctor end -local function create(class_or_instance: string | Instance, props: Props?): ((Props) -> Instance) | Instance - local result: (Props) -> Instance - if type(class_or_instance) == "string" then - result = create_instance(class_or_instance) - elseif typeof(class_or_instance) == "Instance" then - result = clone_instance(class_or_instance) - else - error("bad argument #1, expected string or instance, got " .. typeof(class_or_instance), 0) - return nil :: never - end - if props then - return result(props) - end - return result +type Instances = { + Folder: Folder, + BillboardGui: BillboardGui, + CanvasGroup: CanvasGroup, + Frame: Frame, + ImageButton: ImageButton, + ImageLabel: ImageLabel, + ScreenGui: ScreenGui, + ScrollingFrame: ScrollingFrame, + SurfaceGui: SurfaceGui, + TextBox: TextBox, + TextButton: TextButton, + TextLabel: TextLabel, + UIAspectRatioConstraint: UIAspectRatioConstraint, + UICorner: UICorner, + UIGradient: UIGradient, + UIGridLayout: UIGridLayout, + UIListLayout: UIListLayout, +} + +type function Properties(instance: type?) + local properties = types.newtable() + + while instance do + for i, v in instance:properties() do + local connector = v.read and v.read.tag == "table" and v.read:readproperty(types.singleton("Connect")) + if connector then + if not connector then continue end + local params = connector:parameters().head + if not params then continue end + local listener = params[2] + if not listener then continue end + properties:setproperty(i, types.optional(listener)) + elseif v.write then + properties:setproperty(i, types.optional(types.unionof( + v.write, + types.newfunction({}, { head = { v.write } }) + ))) + end + end + + instance = instance:readparent() + end + + properties:setindexer(types.number, types.any) + + return properties end -type Props = { [any]: any } +type AnyProps = { [any]: any } +type Create = ( + (Instance) -> (AnyProps) -> Instance +) & ( + (string, AnyProps) -> Instance +) & ( + (Name|keyof) -> (Properties>) -> index +) -type Create = ((Name, Props) -> Instance) & ((Name) -> (Props) -> Instance) - -return (create :: any) :: - & ( (T & Instance) -> (Props) -> T ) - & ( (T & Instance, Props) -> T ) - & Create<"Folder", Folder> - & Create<"BillboardGui", BillboardGui> - & Create<"CanvasGroup", CanvasGroup> - & Create<"Frame", Frame> - & Create<"ImageButton", ImageButton> - & Create<"ImageLabel", ImageLabel> - & Create<"ScreenGui", ScreenGui> - & Create<"ScrollingFrame", ScrollingFrame> - & Create<"SurfaceGui", SurfaceGui> - & Create<"TextBox", TextBox> - & Create<"TextButton", TextButton> - & Create<"TextLabel", TextLabel> - & Create<"UIAspectRatioConstraint", UIAspectRatioConstraint> - & Create<"UICorner", UICorner> - & Create<"UIGradient", UIGradient> - & Create<"UIGridLayout", UIGridLayout> - & Create<"UIListLayout", UIListLayout> - & Create<"UIPadding", UIPadding> - & Create<"UIPageLayout", UIPageLayout> - & Create<"UIScale", UIScale> - & Create<"UISizeConstraint", UISizeConstraint> - & Create<"UIStroke", UIStroke> - & Create<"UITableLayout", UITableLayout> - & Create<"UITextSizeConstraint", UITextSizeConstraint> - & Create<"VideoFrame", VideoFrame> - & Create<"ViewportFrame", ViewportFrame> - & Create +return (create :: any) :: Create diff --git a/test/create-types.luau b/test/create-types.luau index 3d387e3..3a1e6fd 100644 --- a/test/create-types.luau +++ b/test/create-types.luau @@ -46,7 +46,7 @@ end local vide = require "../src/" local count = vide.source(0) -create("TextButton") { +create("TextButton") { -- todo: why is `:: "TextButton"` not necessary? BackgroundTransparency = 1, AnchorPoint = "bad value", -- should error InvalidProperty = true, -- should error