Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.