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
|
||||
|
||||
-- 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
|
||||
return nil
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
@@ -325,53 +325,52 @@ function handlers.textDocument_hover(params)
|
||||
end
|
||||
|
||||
if #parts == 0 then
|
||||
return nil
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local current_type = root_type:gsub('"', '')
|
||||
local current_member = nil
|
||||
|
||||
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
|
||||
-- Try built-in types
|
||||
local builtin_params = builtin.get_parameters(current_type)
|
||||
if builtin_params then
|
||||
local fields = builtin_params.inputs 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
|
||||
type_info = { fields = builtin_params.inputs or {}, inputs = builtin_params.inputs or {}, outputs = builtin_params.outputs or {} }
|
||||
end
|
||||
end
|
||||
if current_member then
|
||||
-- Continue to next part
|
||||
else
|
||||
return nil
|
||||
|
||||
if not type_info then
|
||||
return nil, current_type
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
else
|
||||
|
||||
local found = false
|
||||
local fields = type_info.fields or type_info.inputs 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
|
||||
if field.type then
|
||||
current_type = field.type:gsub('"', '')
|
||||
end
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
return nil
|
||||
end
|
||||
return nil, current_type
|
||||
end
|
||||
end
|
||||
|
||||
return current_member
|
||||
return current_member, current_type
|
||||
end
|
||||
|
||||
-- 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 .)
|
||||
if not chain:match("%.") then
|
||||
return nil, nil, chain_start, chain_end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Parse the chain: #var.member1.member2
|
||||
local root_var = chain:match("#([%w_]+)")
|
||||
if not root_var then
|
||||
-- Try without # (for global DB access)
|
||||
root_var = chain:match("^([%w_]+)%.")
|
||||
end
|
||||
-- Parse the chain: #var.member1.member2 or var.member1.member2
|
||||
local root_var = nil
|
||||
local member_path = nil
|
||||
|
||||
-- Try to find #var pattern anywhere in the chain
|
||||
root_var = chain:match("#([%w_]+)")
|
||||
if root_var then
|
||||
local member_path = chain:match("%.(.+)$")
|
||||
return root_var, member_path, chain_start, chain_end
|
||||
-- Find everything after the first #var.
|
||||
member_path = chain:match("#" .. root_var .. "%.(.+)$")
|
||||
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
|
||||
|
||||
-- Try member access first
|
||||
local root_var, member_path, chain_start, chain_end = 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
|
||||
local root_var, member_path = find_member_access_chain()
|
||||
|
||||
if root_type then
|
||||
root_type = root_type:gsub('"', '')
|
||||
-- If we have a member access chain, try to resolve it
|
||||
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 = {}
|
||||
|
||||
-- Combine all type sources
|
||||
@@ -436,13 +451,8 @@ function handlers.textDocument_hover(params)
|
||||
end
|
||||
|
||||
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
|
||||
local detail = string.format("**Member**: `%s`\nType: `%s`",
|
||||
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 },
|
||||
},
|
||||
}
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user