luau icon indicating copy to clipboard operation
luau copied to clipboard

[new solver] table type with concrete fields should qualify as same type with some optional fields.

Open deviaze opened this issue 4 months ago • 0 comments

Tested with latest luau-lsp with all default FFlags enabled.

In this case, I expect the metadata table created in semver.from to be able to qualify as SemverFields.metadata.

local semver = {}
semver.__index = semver

type SemverFields = {
    major: number,
    minor: number,
    patch: number,
    metadata: {
        release_candidate: {
            tag: string,
            ver: number,
        }?,
        build: string?,
    }
}

type SemverImpl = typeof(semver)

export type semver = setmetatable<SemverFields, SemverImpl>

function semver.from(s: string): semver
    local major, minor, patch = string.match(s, "^(%d+)%.(%d+)%.(%d+)")
    if not major or not minor or not patch then
        -- error("Invalid semver string: " .. s)
        major = tonumber(major)
        minor = tonumber(minor)
        patch = tonumber(patch)
        if not major or not minor or not patch then
            error(`Invalid semver string: {s}`)
        end
    end

    local metadata = {}

    -- Extract release candidate (e.g., -rc.1)
    local rc_name, rc_version = string.match(s, "%-(%a+)%.(%d+)")
    if rc_name and rc_version then
        metadata.release_candidate = {
            tag = rc_name,
            ver = tonumber(rc_version) :: number,
        }
    end

    -- Extract build metadata (e.g., +123)
    local build = string.match(s, "%+([%w%.%-]+)")
    if build then
        metadata.build = build
    end

    assert(
        typeof(major) == "number" 
        and typeof(minor) == "number" 
        and typeof(patch) == "number",
        "Unexpected major/minor/patch"
    )

    local fields: SemverFields = {
        major = major,
        minor = minor,
        patch = patch,
        metadata = metadata, -- LUAU FIXME: not optional fields not qualifying/converting to optionals
        --[[
        TypeError: Type
	'{ build: string, release_candidate: { tag: string, ver: number } }'
could not be converted into
	'{ build: string?, release_candidate: { tag: string, ver: number }? }'; 
this is because 
	 * accessing `build` results in `string` and accessing `build` has the 2nd component of the union as `nil`, and `string` is not exactly `nil`
	 * accessing `release_candidate` results in `{ tag: string, ver: number }` and accessing `release_candidate` has the 2nd component of the union as `nil`, and `{ tag: string, ver: number }` is not exactly `nil`
        ]]
    }

    return setmetatable(fields, semver)
end

Link in solverbugs: https://github.com/deviaze/solverbugs/blob/main/src/optionals_not_optionaling.luau

deviaze avatar Aug 13 '25 18:08 deviaze