Skip to content

Commit

Permalink
Merge pull request #107 from gnikit/gnikit/issue62
Browse files Browse the repository at this point in the history
Hover on associated blocks does not display types
  • Loading branch information
gnikit authored May 10, 2022
2 parents 8565d4b + e624a9f commit c7c3a76
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 32 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
### Added

- Added Code of Conduct
- Added basic support for hovering over `ASSOCIATE` blocks
([#62](https://github.com/gnikit/fortls/issues/62))

## 2.3.1

Expand Down
76 changes: 62 additions & 14 deletions fortls/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import copy
import os
import re
from dataclasses import replace
from dataclasses import dataclass, replace
from typing import Pattern

from fortls.constants import (
Expand Down Expand Up @@ -291,6 +291,13 @@ def __init__(
}


@dataclass
class AssociateMap:
var: fortran_var
bind_name: str
link_name: str


class fortran_diagnostic:
def __init__(
self, sline: int, message: str, severity: int = 1, find_word: str = None
Expand Down Expand Up @@ -1365,33 +1372,65 @@ def get_desc(self):
class fortran_associate(fortran_block):
def __init__(self, file_ast: fortran_ast, line_number: int, name: str):
super().__init__(file_ast, line_number, name)
self.assoc_links = []
self.links: list[AssociateMap] = [] # holds the info to associate variables

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

def get_desc(self):
return "ASSOCIATE"

def create_binding_variable(self, file_ast, line_number, bound_name, link_var):
new_var = fortran_var(file_ast, line_number, bound_name, "UNKNOWN", [])
self.assoc_links.append([new_var, bound_name, link_var])
def create_binding_variable(
self, file_ast: fortran_ast, line_number: int, bind_name: str, link_name: str
) -> fortran_var:
"""Create a new variable to be linked upon resolution to the real variable
that contains the information of the mapping from the parent scope to the
ASSOCIATE block scope.
Parameters
----------
file_ast : fortran_ast
AST file
line_number : int
Line number
bind_name : str
Name of the ASSOCIATE block variable
link_name : str
Name of the parent scope variable
Returns
-------
fortran_var
Variable object holding the ASSOCIATE block variable, pending resolution
"""
new_var = fortran_var(file_ast, line_number, bind_name, "UNKNOWN", [])
self.links.append(AssociateMap(new_var, bind_name, link_name))
return new_var

def resolve_link(self, obj_tree):
for assoc_link in self.assoc_links:
var_stack = get_var_stack(assoc_link[2])
if len(var_stack) > 1:
# Loop through the list of the associated variables map and resolve the links
# find the AST node that that corresponds to the variable with link_name
for assoc in self.links:
# TODO: extract the dimensions component from the link_name
# re.sub(r'\(.*\)', '', link_name) removes the dimensions component
# keywords = re.match(r'(.*)\((.*)\)', link_name).groups()
# now pass the keywords through the dimension_parser and set the keywords
# in the associate object. Hover should now pick the local keywords
# over the linked_object keywords
assoc.link_name = re.sub(r"\(.*\)", "", assoc.link_name)
var_stack = get_var_stack(assoc.link_name)
is_member = len(var_stack) > 1
if is_member:
type_scope = climb_type_tree(var_stack, self, obj_tree)
if type_scope is None:
continue
var_obj = find_in_scope(type_scope, var_stack[-1], obj_tree)
if var_obj is not None:
assoc_link[0].link_obj = var_obj
assoc.var.link_obj = var_obj
else:
var_obj = find_in_scope(self, assoc_link[2], obj_tree)
var_obj = find_in_scope(self, assoc.link_name, obj_tree)
if var_obj is not None:
assoc_link[0].link_obj = var_obj
assoc.var.link_obj = var_obj

def require_link(self):
return True
Expand Down Expand Up @@ -1601,6 +1640,7 @@ def get_type_obj(self, obj_tree):
self.type_obj = type_obj
return self.type_obj

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

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

def get_keywords(self):
# TODO: if local keywords are set they should take precedence over link_obj
# Alternatively, I could do a dictionary merge with local variables
# having precedence by default and use a flag to override?
if self.link_obj is not None:
return get_keywords(self.link_obj.keywords, self.link_obj.keyword_info)
return get_keywords(self.keywords, self.keyword_info)

def is_optional(self):
if self.keywords.count(KEYWORD_ID_DICT["optional"]) > 0:
return True
Expand Down
1 change: 1 addition & 0 deletions fortls/parse_fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,7 @@ def parse(
else:
name_raw = var_name.split("=")[0]
# Add dimension if specified
# TODO: turn into function and add support for co-arrays i.e. [*]
key_tmp = obj_info.keywords[:]
iparen = name_raw.find("(")
if iparen == 0:
Expand Down
1 change: 1 addition & 0 deletions test/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def check_return(result_array):
objs = (
["test", 6, 7],
["test_abstract", 2, 0],
["test_associate_block", 2, 0],
["test_free", 2, 0],
["test_gen_type", 5, 1],
["test_generic", 2, 0],
Expand Down
12 changes: 12 additions & 0 deletions test/test_server_hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,15 @@ def test_hover_interface_as_argument():
REAL :: arg3""",
)
validate_hover(results, ref_results)


def test_hover_block():
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "hover")})
file_path = test_dir / "hover" / "associate_block.f90"
string += hover_req(file_path, 4, 17)
string += hover_req(file_path, 4, 20)
# string += hover_req(file_path, 10, 11) # slice of array
errcode, results = run_request(string, fortls_args=["--sort_keywords", "-n", "1"])
assert errcode == 0
ref_results = ["REAL, DIMENSION(5)", "REAL"]
validate_hover(results, ref_results)
13 changes: 13 additions & 0 deletions test/test_source/hover/associate_block.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PROGRAM test_associate_block
IMPLICIT NONE
REAL :: A(5), B(5,5), C, III = 1
ASSOCIATE (X => A, Y => C)
PRINT*, X, Y, III
END ASSOCIATE
ASSOCIATE (X => 1)
PRINT*, X
END ASSOCIATE
ASSOCIATE (ARRAY => B(:,1))
ARRAY (3) = ARRAY (1) + ARRAY (2)
END ASSOCIATE
END PROGRAM test_associate_block
18 changes: 0 additions & 18 deletions test/test_source/tmp.py

This file was deleted.

0 comments on commit c7c3a76

Please sign in to comment.