Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setmetatable isn't inferring correct return type with generics #1098

Open
JonhIsh opened this issue Nov 10, 2023 · 1 comment
Open

setmetatable isn't inferring correct return type with generics #1098

JonhIsh opened this issue Nov 10, 2023 · 1 comment
Labels
bug Something isn't working

Comments

@JonhIsh
Copy link

JonhIsh commented Nov 10, 2023

Hey, so I was trying to write a module script which would have a number of functions we'd normally get out of language libraries in OOP languages for arrays, but in this case adapted for Dictionaries.. So I did the following minimal reproduction of this error:

--!strict
type DictionaryImpl<K,V> =	{
	__index: DictionaryImpl<K,V>,
	Get: (self: Dictionary<K, V>, key: K) -> V,
}

type Dictionary<K, V> = typeof(setmetatable({} :: {[K]: V}, {} :: DictionaryImpl<K,V>))

function New<K,V>(objects: {[K]: V}): Dictionary<K,V>
	local newMetatable: DictionaryImpl<K, V> = {} :: DictionaryImpl<K, V>
	
	function newMetatable:Get(key: K)
		return self[key]
	end

	newMetatable.__index = newMetatable
	return setmetatable(objects, newMetatable)
end

type stuffType = {
	[string]: number
}

local testStuff: stuffType = {stuff = 123}

local testDictionary = New(testStuff)
print(testDictionary:Get("stuff"))

When doing testDictionary:Get("stuff"), it works as expected and returns me the value = 123, but when I hover my mouse over testDictionary it just says the type is of Dictionary rather than Dictionary<string, number> which it should since it is inferring those types quite well (In this case, intelisense tells me that Get receives a parameter key of type string and returns something of type number, so it is correct). Personally that wouldn't be a problem but what is a problem is that when I do write testDictionary:Get("stuff") it tells me that "Cannot call non-function member".

So my two problems boil down to this:

  1. It tells me that Get is a non-function member, when it is defined as a function.
  2. Intelisense displays __index as a member of my table.. shouldn't this be hidden?

I do however wanna clarify some things that I have done that I know are bizarre. The reason why I am defining the metatable contents (such as __index and the Get method) is that if I do in the conventional way (as described in Luau's type checking documentation and showcased below), then I would have to instantiate my table Dictionary as being local Dictionary: Dictionary<unknown, unknown> = {} :: DictionaryImpl<unknown,unknown>.. and from this point forward I lose all type inference for K and V, which is one of my main goals with this whole thing to begin with.

type AccountImpl = {
    __index: AccountImpl,
    new: (name: string, balance: number) -> Account,
    deposit: (self: Account, credit: number) -> (),
    withdraw: (self: Account, debit: number) -> (),
}

type Account = typeof(setmetatable({} :: { name: string, balance: number }, {} :: AccountImpl))

-- Only these two annotations are necessary
local Account: AccountImpl = {} :: AccountImpl
Account.__index = Account

-- Using the knowledge of `Account`, we can take in information of the `new` type from `AccountImpl`, so:
-- Account.new :: (name: string, balance: number) -> Account
function Account.new(name, balance)
    local self = {}
    self.name = name
    self.balance = balance

    return setmetatable(self, Account)
end

-- Ditto:
-- Account:deposit :: (self: Account, credit: number) -> ()
function Account:deposit(credit)
    self.balance += credit
end

-- Ditto:
-- Account:withdraw :: (self: Account, debit: number) -> ()
function Account:withdraw(debit)
    self.balance -= debit
end

local account = Account.new("Alexander", 500)
@JonhIsh JonhIsh added the bug Something isn't working label Nov 10, 2023
@JonhIsh
Copy link
Author

JonhIsh commented Nov 20, 2023

Bumping this since it isn't getting any replies

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Development

No branches or pull requests

1 participant