Benjamin-Dobell / IntelliJ-Luanalysis

Type-safe Lua IDE — IntelliJ IDEA plugin
Apache License 2.0
155 stars 22 forks source link

Type mismatch error when assigning subclasses to an array of superclass #146

Closed pouwelsjochem closed 1 year ago

pouwelsjochem commented 1 year ago

Environment

Name Version
IDEA version 2022.3.1
Luanalysis version 1.4.0
OS MacOS

Lua

Name Setting
Language level 5.2

Type Safety

Name Setting
Strict nil checks
Unknown type (any) is indexable ☑️
Unknown type (any) is callabale ☑️

What are the steps to reproduce this issue?

    ---@class MyClass
    ---@class ExtendedMyClass : MyClass

    local class ---@type MyClass
    local classArray ---@type MyClass[]

    local function createExtendedClass()
        return --[[---@type ExtendedMyClass]] {}
    end
    local function createExtendedClassArray()
        return --[[---@type ExtendedMyClass[] ]] {}
    end
    local function createClassArray()
        return --[[---@type MyClass[] ]] {}
    end

    class = createExtendedClass()
    classArray = createExtendedClassArray() -- Type mismatch. Required: 'MyClass[]' Found: 'ExtendedMyClass[]
    classArray = createClassArray()

What happens?

When assigning a ExtendedMyClass[] to a variable with MyClass[] type there's an error saying: Type mismatch. Required: 'MyShape[]' Found: 'ExtendedMyShape[]. This works fine when its not an array and we're just assigning a ExtendedMyClass to a variable with MyClass type.

What were you expecting to happen?

No error to be given when assigning ExtendedMyClass[] to MyClass[] since this works on non-array variables already and ExtendedMyClass should include all properties that exist on MyClass.

Benjamin-Dobell commented 1 year ago

Consider:

---@class MyClass
---@field type string

---@class ExtendedMyClassA : MyClass
---@field fieldA string

---@class ExtendedMyClassB : MyClass
---@field fieldB boolean

function createExtendedClassA()
    return --[[---@type ExtendedMyClassA]] {
        type = "Class A",
        fieldA = "foo"
    }
end

function createExtendedClassB()
    return --[[---@type ExtendedMyClassB]] {
        type = "Class B",
        fieldB = true
    }
end

---@type ExtendedMyClassA[]
arrayOfClassA = {
    createExtendedClassA()
}

---@param arr MyClass[]
function injectClassB(arr)
    table.insert(arr, createExtendedClassB())
end

injectClassB(arrayOfClassA)

for _, myClassA in ipairs(arrayOfClassA) do
    print(myClassA.fieldA:sub(1))
end

Luanalysis is correctly reporting the error:

image

Which prevents (or rather highlights) the runtime crash:

image

However, you can still work with arrays that contain subtypes with the assistance of generics e.g. the following is just fine:

---@class MyClass
---@field type string

---@class ExtendedMyClassA : MyClass
---@field fieldA string

---@class ExtendedMyClassB : MyClass
---@field fieldB boolean

function createExtendedClassA()
    return --[[---@type ExtendedMyClassA]] {
        type = "Class A",
        fieldA = "foo"
    }
end

function createExtendedClassB()
    return --[[---@type ExtendedMyClassB]] {
        type = "Class B",
        fieldB = true
    }
end

---@type ExtendedMyClassA[]
arrayOfClassA = {
    createExtendedClassA()
}

---@type ExtendedMyClassB[]
arrayOfClassB = {
    createExtendedClassB()
}

---@type MyClass[]
arrayOfMyClass = {
    createExtendedClassA(),
    createExtendedClassB()
}

---@generic T : MyClass
---@param arrayOfMyClass T[]
function doSomethingWithMyClassArray(arrayOfMyClass)
    for _, myClass in ipairs(arrayOfMyClass) do
        print(myClass.type)
    end
end

doSomethingWithMyClassArray(arrayOfClassA)
doSomethingWithMyClassArray(arrayOfClassB)
doSomethingWithMyClassArray(arrayOfMyClass)
pouwelsjochem commented 1 year ago

Ah I see the problem with that, thank you for elaborating the possible issues with my suggestion solution. Using generics is the way to go then :)