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

Unexpected type mismatch when assigning a generic function. #84

Closed
vsaulue opened this issue Jun 22, 2021 · 9 comments
Closed

Unexpected type mismatch when assigning a generic function. #84

vsaulue opened this issue Jun 22, 2021 · 9 comments
Labels
enhancement New feature or request tooltips Pertains to tooltip documentation that displays when you hover over an element.

Comments

@vsaulue
Copy link

vsaulue commented Jun 22, 2021

Environment

name version
IDEA version 2021.1.1 Community Edition (build #IC-211.7142.45)
Luanalysis version 1.2.3
OS Windows 10

What are the steps to reproduce this issue?

Create a Lua file with the following code:

---@generic T
---@param val T
---@return T
local function foobar(val) 
    return val 
end

---@type fun<T>(val:T):T
local x = foobar

What happens?

Luanalysis generates a "Type mismatch" error on line local x = foobar:

Type mismatch. Required: 'fun(val: T): T' Found: 'fun(val: T): T'

image

What were you expecting to happen?

No error: this should be a valid assign.

Any logs, error output, etc?

None

@Benjamin-Dobell
Copy link
Owner

Benjamin-Dobell commented Jun 22, 2021

Generic parameter types are only accessible within the scope in which they're defined. The two different T refer to different types.

I'm not aware of any languages that allow this sort of semantic as assignment typically deals with concrete (runtime) types, and generics are of course abstract ("compile" time / static) types.

Can you give an example where this pattern might be useful?

@vsaulue
Copy link
Author

vsaulue commented Jun 22, 2021

A hard to workaround situation is when forward declarations are needed for circular references. Like 2 functions calling each other:

---@type fun<T>(array:T, index:number)
local fun1
---@type fun<T>(array:T, index:number)
local fun2

--- @generic T
--- @param array T[]
--- @param index number
fun1 = function(array, index)
    -- Needs to call fun2
end

--- @generic T
--- @param array T[]
--- @param index number
fun2 = function(array, index)
    -- Needs to call fun1
end

Even when it's not needed for circular references, I like to systematically forward-declare all file-scope functions at the beginning of a file. I find it extremely convenient (and less bug prone) to be able to implement the body of a function anywhere in a file, without having to deal with global 'foobar' cannot be called (nil value) because foobar is declared 100 lines later.

@Benjamin-Dobell
Copy link
Owner

Benjamin-Dobell commented Jun 22, 2021

Typing the forward declaration is correct, however the duplicate type definitions that follow are unnecessary. Having duplicate type definitions will create a fairly significant maintenance burden, particularly since you're unlikely to be able to take advantage of any refactoring tools.

The second set of type definitions belong to the anonymous function, not fun1 and fun2. So there's no real benefit, function and parameter descriptions belonging to the anonymous function won't be displayed when referencing fun1 and fun2.

I think perhaps what you're after is the ability to (forward) declare a variable type using the (multi-line) function doc syntax. That way you'd be able to include function and parameter descriptions. This would also solve another problem we have at present where you can't define a variable as having overloads.

EDIT: In saying that there's currently no means to provide descriptions for @overload's themselves or their parameters. So at some point we need a more verbose means of typing overloads too.

@vsaulue
Copy link
Author

vsaulue commented Jun 22, 2021

Looking at your first 2 paragraphs, the current supported solution, in terms of type safety, would be:

---@type fun<T>(array:T, index:number)
local fun1

fun1 = function(array, index)
    -- Needs to call fun2
end

So I would have to get rid of the parameters/returns documentation, which isn't satisfying.

I think perhaps what you're after is the ability to (forward) declare a variable type using the (multi-line) function doc syntax. That way you'd be able to include function and parameter descriptions. This would also solve another problem we have at present where you can't define a variable as having overloads.

If you're proposing to be supporting something like this:

--- This is fun1 documentation.
--- @generic T
--- @param array T[] @Arg documentation
--- @param index number @Index documentation
--- @return T[] @Result documentation
local fun1 = function(array,index) end -- <--- no complain from lack of body

fun1 = function(array, index)
    -- Inferred types:
    -- array: T[]
    -- index: number
    return {} -- T[]
end

I could indeed easily workaround the bug.

@Benjamin-Dobell
Copy link
Owner

Benjamin-Dobell commented Jun 22, 2021

Looking at your first 2 paragraphs, the current supported solution, in terms of type safety, would be...

Correct.

So I would have to get rid of the parameters/returns documentation, which isn't satisfying.

Whilst I agree that they ought to be supported (for forward declarations). As mentioned, they are reasonably useless how you're presently using them, as the documentation belongs to the anonymous functions, and thus is only visible within the function scope. That's not to say regular documentation is pointless, it's just using the type doc annotation syntax doesn't provide much value in this context.

I could indeed easily workaround the bug.

Feature request, not bug. However, I'm proposing:

--- This is fun1 documentation.
--- @generic T
--- @param array T[] @Arg documentation
--- @param index number @Index documentation
--- @return T[] @Result documentation
local fun1

fun1 = function(array, index)
    -- Inferred types:
    -- array: T[]
    -- index: number
    return {} -- T[]
end

i.e. no superfluous function body required.

@Benjamin-Dobell Benjamin-Dobell added enhancement New feature or request tooltips Pertains to tooltip documentation that displays when you hover over an element. labels Jun 22, 2021
@vsaulue
Copy link
Author

vsaulue commented Jul 1, 2021

Thanks for the fast answers.

For now I'll use the @type annotation, and type the documentation without @ param/@ return/@ generic. This way I can get type safety and still decent documentation.

Is there a way to refer to generic T in the function bodies with the @type trick ?

---@type fun<T>(v:T[])
local fun1

fun1 = function(v)
    ---@type T|nil
    local r
end

image

@Benjamin-Dobell
Copy link
Owner

Is there a way to refer to generic T in the function bodies with the @type trick ?

Unfortunately not. I'd like to add something like @typeof v at some point.

For now the only way to get to that type would be through absurd hacks like:

---@type fun<T>(v:T[])
local fun1

---@type fun<T>(arr: T[]): nil | T
local nilElementType = function(arr) return nil end

fun1 = function(v)
    local r = nilElementType(v)
end

Screen Shot 2021-07-02 at 3 23 41 am

Yeah... maybe if you're desperate 🤷‍♂️

@adriweb
Copy link

adriweb commented Jul 1, 2021

+1 for @typeof, I'd have used that several times if it were a thing :D

@Benjamin-Dobell
Copy link
Owner

Benjamin-Dobell commented Sep 18, 2021

I resolved the original issue, assigning a generic function to a compatible generic function is now possible. So forward declarations can be used as originally desired.

However, there's still the issue with forward declarations not being able to be documented. I've opened #103 to track function type definition limitations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request tooltips Pertains to tooltip documentation that displays when you hover over an element.
Projects
None yet
Development

No branches or pull requests

3 participants