fix: improve member access hover resolution
- Fix chain detection to properly extract #var and member path - Check all type sources (doc.types, external_types, builtins) for resolution - Return helpful error messages when type resolution fails - Prevent fallthrough to type check when member access is detected - Show which type couldn't be found in resolution chain
This commit is contained in:
+68
-45
@@ -314,9 +314,9 @@ function handlers.textDocument_hover(params)
|
|||||||
local col = params.position.character + 1
|
local col = params.position.character + 1
|
||||||
|
|
||||||
-- Helper to resolve member path and get member info with comments
|
-- Helper to resolve member path and get member info with comments
|
||||||
local function resolve_member_path(root_type, member_path, types)
|
local function resolve_member_path(root_type, member_path, types, external_types_lookup)
|
||||||
if not root_type or not member_path or member_path == "" then
|
if not root_type or not member_path or member_path == "" then
|
||||||
return nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local parts = {}
|
local parts = {}
|
||||||
@@ -325,53 +325,52 @@ function handlers.textDocument_hover(params)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if #parts == 0 then
|
if #parts == 0 then
|
||||||
return nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local current_type = root_type:gsub('"', '')
|
local current_type = root_type:gsub('"', '')
|
||||||
local current_member = nil
|
local current_member = nil
|
||||||
|
|
||||||
for i, part in ipairs(parts) do
|
for i, part in ipairs(parts) do
|
||||||
local type_info = types and types[current_type]
|
-- Get type info from combined sources
|
||||||
|
local type_info = types[current_type]
|
||||||
|
|
||||||
|
-- Also check external types if not found
|
||||||
|
if not type_info and external_types_lookup then
|
||||||
|
type_info = external_types_lookup[current_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try built-in types as fallback
|
||||||
if not type_info then
|
if not type_info then
|
||||||
-- Try built-in types
|
|
||||||
local builtin_params = builtin.get_parameters(current_type)
|
local builtin_params = builtin.get_parameters(current_type)
|
||||||
if builtin_params then
|
if builtin_params then
|
||||||
local fields = builtin_params.inputs or {}
|
type_info = { fields = builtin_params.inputs or {}, inputs = builtin_params.inputs or {}, outputs = builtin_params.outputs or {} }
|
||||||
for _, field in ipairs(fields) do
|
|
||||||
if field.name == part then
|
|
||||||
current_member = field
|
|
||||||
current_type = field.type and field.type:gsub('"', '') or current_type
|
|
||||||
break
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if current_member then
|
|
||||||
-- Continue to next part
|
if not type_info then
|
||||||
else
|
return nil, current_type
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local found = false
|
local found = false
|
||||||
local fields = type_info.fields or type_info.inputs or {}
|
local fields = type_info.fields or type_info.inputs or {}
|
||||||
for _, field in ipairs(fields) do
|
for _, field in ipairs(fields) do
|
||||||
if field.name == part then
|
if field.name == part then
|
||||||
current_member = field
|
current_member = field
|
||||||
current_type = field.type and field.type:gsub('"', '') or current_type
|
if field.type then
|
||||||
|
current_type = field.type:gsub('"', '')
|
||||||
|
end
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not found then
|
if not found then
|
||||||
return nil
|
return nil, current_type
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return current_member
|
return current_member, current_type
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if we're in a member access chain (e.g., #var.member1.member2)
|
-- Check if we're in a member access chain (e.g., #var.member1.member2)
|
||||||
@@ -400,32 +399,48 @@ function handlers.textDocument_hover(params)
|
|||||||
|
|
||||||
-- Check if this is a member access (contains .)
|
-- Check if this is a member access (contains .)
|
||||||
if not chain:match("%.") then
|
if not chain:match("%.") then
|
||||||
return nil, nil, chain_start, chain_end
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parse the chain: #var.member1.member2
|
-- Parse the chain: #var.member1.member2 or var.member1.member2
|
||||||
local root_var = chain:match("#([%w_]+)")
|
local root_var = nil
|
||||||
if not root_var then
|
local member_path = nil
|
||||||
-- Try without # (for global DB access)
|
|
||||||
root_var = chain:match("^([%w_]+)%.")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
-- Try to find #var pattern anywhere in the chain
|
||||||
|
root_var = chain:match("#([%w_]+)")
|
||||||
if root_var then
|
if root_var then
|
||||||
local member_path = chain:match("%.(.+)$")
|
-- Find everything after the first #var.
|
||||||
return root_var, member_path, chain_start, chain_end
|
member_path = chain:match("#" .. root_var .. "%.(.+)$")
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil, nil, chain_start, chain_end
|
-- If no # found, try global DB access (starts with identifier)
|
||||||
|
if not root_var then
|
||||||
|
root_var = chain:match("^([%w_]+)%.")
|
||||||
|
if root_var then
|
||||||
|
member_path = chain:match("%.(.+)$")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if root_var and member_path then
|
||||||
|
return root_var, member_path
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Try member access first
|
-- Try member access first
|
||||||
local root_var, member_path, chain_start, chain_end = find_member_access_chain()
|
local root_var, member_path = find_member_access_chain()
|
||||||
if root_var and member_path and doc.variables and doc.variables[root_var] then
|
|
||||||
local var_info = doc.variables[root_var]
|
|
||||||
local root_type = var_info.data_type
|
|
||||||
|
|
||||||
if root_type then
|
-- If we have a member access chain, try to resolve it
|
||||||
root_type = root_type:gsub('"', '')
|
if root_var and member_path then
|
||||||
|
-- Check if it's a local variable (with #) or global
|
||||||
|
local var_info = nil
|
||||||
|
if doc.variables and doc.variables[root_var] then
|
||||||
|
var_info = doc.variables[root_var]
|
||||||
|
end
|
||||||
|
|
||||||
|
if var_info and var_info.data_type then
|
||||||
|
local root_type = var_info.data_type:gsub('"', '')
|
||||||
local all_types = {}
|
local all_types = {}
|
||||||
|
|
||||||
-- Combine all type sources
|
-- Combine all type sources
|
||||||
@@ -436,13 +451,8 @@ function handlers.textDocument_hover(params)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local external_types = plc_json.get_types(doc.root_dir or "")
|
local external_types = plc_json.get_types(doc.root_dir or "")
|
||||||
for k, v in pairs(external_types) do
|
|
||||||
if not all_types[k] then
|
|
||||||
all_types[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local member = resolve_member_path(root_type, member_path, all_types)
|
local member, failed_at = resolve_member_path(root_type, member_path, all_types, external_types)
|
||||||
if member then
|
if member then
|
||||||
local detail = string.format("**Member**: `%s`\nType: `%s`",
|
local detail = string.format("**Member**: `%s`\nType: `%s`",
|
||||||
member.name, member.type or "unknown")
|
member.name, member.type or "unknown")
|
||||||
@@ -468,7 +478,20 @@ function handlers.textDocument_hover(params)
|
|||||||
["end"] = { line = params.position.line, character = member_word_end - 1 },
|
["end"] = { line = params.position.line, character = member_word_end - 1 },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
elseif failed_at then
|
||||||
|
-- Type resolution failed - show what we know
|
||||||
|
local detail = string.format("**Unresolved Member**\nVariable: `#%s`\nType: `%s`\nPath: `%s`\n\n*Type not found: `%s`*",
|
||||||
|
root_var, root_type, member_path, failed_at)
|
||||||
|
return {
|
||||||
|
contents = { kind = "markdown", value = detail },
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
-- Variable not found
|
||||||
|
local detail = string.format("**Unknown Variable**: `#%s`\n\nVariable not declared in scope.", root_var)
|
||||||
|
return {
|
||||||
|
contents = { kind = "markdown", value = detail },
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user