Skip to content

Commit 11649f0

Browse files
committed
Hover on associated blocks does not display types
Fixes #62 Adds rudimentary hover support for hovering over associate blocks TODO: - Support slices of existing AST nodes, currently the associate node inherits the keywords from the link_obj so the slices display the wrong dimensions. - Support literals, given that Fortran literals are not currently registered as AST nodes it is difficult to provide information on the fly without creating more spaghetti code (see current literals). The only elegant solution I can think of is parsing the literals as AST nodes.
1 parent 8565d4b commit 11649f0

File tree

5 files changed

+88
-32
lines changed

5 files changed

+88
-32
lines changed

fortls/objects.py

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import copy
44
import os
55
import re
6-
from dataclasses import replace
6+
from dataclasses import dataclass, replace
77
from typing import Pattern
88

99
from fortls.constants import (
@@ -291,6 +291,13 @@ def __init__(
291291
}
292292

293293

294+
@dataclass
295+
class AssociateMap:
296+
var: fortran_var
297+
bind_name: str
298+
link_name: str
299+
300+
294301
class fortran_diagnostic:
295302
def __init__(
296303
self, sline: int, message: str, severity: int = 1, find_word: str = None
@@ -1365,33 +1372,65 @@ def get_desc(self):
13651372
class fortran_associate(fortran_block):
13661373
def __init__(self, file_ast: fortran_ast, line_number: int, name: str):
13671374
super().__init__(file_ast, line_number, name)
1368-
self.assoc_links = []
1375+
self.links: list[AssociateMap] = [] # holds the info to associate variables
13691376

13701377
def get_type(self, no_link=False):
13711378
return ASSOC_TYPE_ID
13721379

13731380
def get_desc(self):
13741381
return "ASSOCIATE"
13751382

1376-
def create_binding_variable(self, file_ast, line_number, bound_name, link_var):
1377-
new_var = fortran_var(file_ast, line_number, bound_name, "UNKNOWN", [])
1378-
self.assoc_links.append([new_var, bound_name, link_var])
1383+
def create_binding_variable(
1384+
self, file_ast: fortran_ast, line_number: int, bind_name: str, link_name: str
1385+
) -> fortran_var:
1386+
"""Create a new variable to be linked upon resolution to the real variable
1387+
that contains the information of the mapping from the parent scope to the
1388+
ASSOCIATE block scope.
1389+
1390+
Parameters
1391+
----------
1392+
file_ast : fortran_ast
1393+
AST file
1394+
line_number : int
1395+
Line number
1396+
bind_name : str
1397+
Name of the ASSOCIATE block variable
1398+
link_name : str
1399+
Name of the parent scope variable
1400+
1401+
Returns
1402+
-------
1403+
fortran_var
1404+
Variable object holding the ASSOCIATE block variable, pending resolution
1405+
"""
1406+
new_var = fortran_var(file_ast, line_number, bind_name, "UNKNOWN", [])
1407+
self.links.append(AssociateMap(new_var, bind_name, link_name))
13791408
return new_var
13801409

13811410
def resolve_link(self, obj_tree):
1382-
for assoc_link in self.assoc_links:
1383-
var_stack = get_var_stack(assoc_link[2])
1384-
if len(var_stack) > 1:
1411+
# Loop through the list of the associated variables map and resolve the links
1412+
# find the AST node that that corresponds to the variable with link_name
1413+
for assoc in self.links:
1414+
# TODO: extract the dimensions component from the link_name
1415+
# re.sub(r'\(.*\)', '', link_name) removes the dimensions component
1416+
# keywords = re.match(r'(.*)\((.*)\)', link_name).groups()
1417+
# now pass the keywords through the dimension_parser and set the keywords
1418+
# in the associate object. Hover should now pick the local keywords
1419+
# over the linked_object keywords
1420+
assoc.link_name = re.sub(r"\(.*\)", "", assoc.link_name)
1421+
var_stack = get_var_stack(assoc.link_name)
1422+
is_member = len(var_stack) > 1
1423+
if is_member:
13851424
type_scope = climb_type_tree(var_stack, self, obj_tree)
13861425
if type_scope is None:
13871426
continue
13881427
var_obj = find_in_scope(type_scope, var_stack[-1], obj_tree)
13891428
if var_obj is not None:
1390-
assoc_link[0].link_obj = var_obj
1429+
assoc.var.link_obj = var_obj
13911430
else:
1392-
var_obj = find_in_scope(self, assoc_link[2], obj_tree)
1431+
var_obj = find_in_scope(self, assoc.link_name, obj_tree)
13931432
if var_obj is not None:
1394-
assoc_link[0].link_obj = var_obj
1433+
assoc.var.link_obj = var_obj
13951434

13961435
def require_link(self):
13971436
return True
@@ -1601,6 +1640,7 @@ def get_type_obj(self, obj_tree):
16011640
self.type_obj = type_obj
16021641
return self.type_obj
16031642

1643+
# XXX: unused delete or use for associate blocks
16041644
def set_dim(self, dim_str):
16051645
if KEYWORD_ID_DICT["dimension"] not in self.keywords:
16061646
self.keywords.append(KEYWORD_ID_DICT["dimension"])
@@ -1618,9 +1658,9 @@ def get_snippet(self, name_replace=None, drop_arg=-1):
16181658

16191659
def get_hover(self, long=False, include_doc=True, drop_arg=-1):
16201660
doc_str = self.get_documentation()
1621-
hover_str = ", ".join(
1622-
[self.desc] + get_keywords(self.keywords, self.keyword_info)
1623-
)
1661+
# In associated blocks we need to fetch the desc and keywords of the
1662+
# linked object
1663+
hover_str = ", ".join([self.get_desc()] + self.get_keywords())
16241664
# TODO: at this stage we can mae this lowercase
16251665
# Add parameter value in the output
16261666
if self.is_parameter() and self.param_val:
@@ -1629,6 +1669,14 @@ def get_hover(self, long=False, include_doc=True, drop_arg=-1):
16291669
hover_str += "\n {0}".format("\n ".join(doc_str.splitlines()))
16301670
return hover_str, True
16311671

1672+
def get_keywords(self):
1673+
# TODO: if local keywords are set they should take precedence over link_obj
1674+
# Alternatively, I could do a dictionary merge with local variables
1675+
# having precedence by default and use a flag to override?
1676+
if self.link_obj is not None:
1677+
return get_keywords(self.link_obj.keywords, self.link_obj.keyword_info)
1678+
return get_keywords(self.keywords, self.keyword_info)
1679+
16321680
def is_optional(self):
16331681
if self.keywords.count(KEYWORD_ID_DICT["optional"]) > 0:
16341682
return True

fortls/parse_fortran.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,6 +1357,7 @@ def parse(
13571357
else:
13581358
name_raw = var_name.split("=")[0]
13591359
# Add dimension if specified
1360+
# TODO: turn into function and add support for co-arrays i.e. [*]
13601361
key_tmp = obj_info.keywords[:]
13611362
iparen = name_raw.find("(")
13621363
if iparen == 0:

test/test_server_hover.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,15 @@ def test_hover_interface_as_argument():
315315
REAL :: arg3""",
316316
)
317317
validate_hover(results, ref_results)
318+
319+
320+
def test_hover_block():
321+
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "hover")})
322+
file_path = test_dir / "hover" / "associate_block.f90"
323+
string += hover_req(file_path, 4, 17)
324+
string += hover_req(file_path, 4, 20)
325+
# string += hover_req(file_path, 10, 11) # slice of array
326+
errcode, results = run_request(string, fortls_args=["--sort_keywords", "-n", "1"])
327+
assert errcode == 0
328+
ref_results = ["REAL, DIMENSION(5)", "REAL"]
329+
validate_hover(results, ref_results)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
PROGRAM associate_block_test
2+
IMPLICIT NONE
3+
REAL :: A(5), B(5,5), C, III = 1
4+
ASSOCIATE (X => A, Y => C)
5+
PRINT*, X, Y, III
6+
END ASSOCIATE
7+
ASSOCIATE (X => 1)
8+
PRINT*, X
9+
END ASSOCIATE
10+
ASSOCIATE (ARRAY => B(:,1))
11+
ARRAY (3) = ARRAY (1) + ARRAY (2)
12+
END ASSOCIATE
13+
END PROGRAM associate_block_test

test/test_source/tmp.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)