Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: len and kind attribute mistaken as variables #238

Merged
merged 6 commits into from
Nov 17, 2022
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
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ omit =
fortls/__init__.py
fortls/version.py
fortls/schema.py
concurrency = multiprocessing
parallel = true
sigterm = true

[report]
exclude_lines =
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

### Fixed

- Fixed bug where diagnostic messages were raised for non-existent variables
([#173](https://github.com/fortran-lang/fortls/issues/173))
([#175](https://github.com/fortran-lang/fortls/issues/175))
- Fixed submodule crashing bug and document/Symbol request failure
([#233](https://github.com/fortran-lang/fortls/issues/233))
- Fixed debug interface parser not loading all configuration files
Expand Down
8 changes: 5 additions & 3 deletions fortls/ftypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class VarInfo:
#: keywords associated with this variable e.g. SAVE, DIMENSION, etc.
keywords: list[str] #: Keywords associated with variable
var_names: list[str] #: Variable names
var_kind: str = field(default=None) #: Kind of variable e.g. ``INTEGER*4`` etc.
#: Kind of variable e.g. ``INTEGER*4`` etc.
var_kind: str | None = field(default=None)


@dataclass
Expand Down Expand Up @@ -106,8 +107,9 @@ class SubInfo:
class ResultSig:
"""Holds information about the RESULT section of a Fortran FUNCTION"""

name: str = field(default=None) #: Variable name of result
type: str = field(default=None) #: Variable type of result
name: str | None = field(default=None) #: Variable name of result
type: str | None = field(default=None) #: Variable type of result
kind: str | None = field(default=None) #: Variable kind of result
#: Keywords associated with the result variable, can append without init
keywords: list[str] = field(default_factory=list)

Expand Down
38 changes: 27 additions & 11 deletions fortls/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,7 @@ def __init__(
var_desc: str,
keywords: list,
keyword_info: dict = None,
# kind: int | str = None,
kind: str | None = None,
link_obj=None,
):
super().__init__()
Expand All @@ -1604,7 +1604,7 @@ def __init__(
self.desc: str = var_desc
self.keywords: list = keywords
self.keyword_info: dict = keyword_info
self.callable: bool = FRegex.CLASS_VAR.match(var_desc) is not None
self.kind: str | None = kind
self.children: list = []
self.use: list[USE_line] = []
self.link_obj = None
Expand All @@ -1613,7 +1613,7 @@ def __init__(
self.is_external: bool = False
self.param_val: str = None
self.link_name: str = None
# self.kind: int | str = kind
self.callable: bool = FRegex.CLASS_VAR.match(self.get_desc(True)) is not None
self.FQSN: str = self.name.lower()
if link_obj is not None:
self.link_name = link_obj.lower()
Expand Down Expand Up @@ -1657,17 +1657,19 @@ def get_type(self, no_link=False):
# Normal variable
return VAR_TYPE_ID

def get_desc(self):
if self.link_obj is not None:
def get_desc(self, no_link=False):
if not no_link and self.link_obj is not None:
return self.link_obj.get_desc()
# Normal variable
if self.kind:
return self.desc + self.kind
return self.desc

def get_type_obj(self, obj_tree):
if self.link_obj is not None:
return self.link_obj.get_type_obj(obj_tree)
if (self.type_obj is None) and (self.parent is not None):
type_name = get_paren_substring(self.desc)
type_name = get_paren_substring(self.get_desc(no_link=True))
if type_name is not None:
search_scope = self.parent
if search_scope.get_type() == CLASS_TYPE_ID:
Expand Down Expand Up @@ -1739,19 +1741,25 @@ def set_external_attr(self):

def check_definition(self, obj_tree, known_types={}, interface=False):
# Check for type definition in scope
type_match = FRegex.DEF_KIND.match(self.desc)
type_match = FRegex.DEF_KIND.match(self.get_desc(no_link=True))
if type_match is not None:
var_type = type_match.group(1).strip().lower()
if var_type == "procedure":
return None, known_types
desc_obj_name = type_match.group(2).strip().lower()
if desc_obj_name not in known_types:
type_def = find_in_scope(
self.parent, desc_obj_name, obj_tree, interface=interface
self.parent,
desc_obj_name,
obj_tree,
interface=interface,
)
if type_def is None:
type_defs = find_in_workspace(
obj_tree, desc_obj_name, filter_public=True, exact_match=True
obj_tree,
desc_obj_name,
filter_public=True,
exact_match=True,
)
known_types[desc_obj_name] = None
var_type = type_match.group(1).strip().lower()
Expand Down Expand Up @@ -1802,15 +1810,23 @@ def __init__(
var_desc: str,
keywords: list,
keyword_info: dict,
proc_ptr: str = "", # procedure pointer e.g. `foo` in `procedure(foo)`
link_obj=None,
):
super().__init__(
file_ast, line_number, name, var_desc, keywords, keyword_info, link_obj
file_ast,
line_number,
name,
var_desc,
keywords,
keyword_info,
kind=proc_ptr,
link_obj=link_obj,
)
self.drop_arg: int = -1
self.pass_name: str = keyword_info.get("pass")
if link_obj is None:
self.link_name = get_paren_substring(var_desc.lower())
self.link_name = get_paren_substring(self.get_desc(True).lower())

def set_parent(self, parent_obj):
self.parent = parent_obj
Expand Down
23 changes: 15 additions & 8 deletions fortls/parse_fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def parse_var_keywords(test_str: str) -> tuple[list[str], str]:
return keywords, test_str


def read_var_def(line: str, var_type: str = None, fun_only: bool = False):
def read_var_def(line: str, var_type: str | None = None, fun_only: bool = False):
"""Attempt to read variable definition line"""

def parse_kind(line: str):
Expand Down Expand Up @@ -202,22 +202,22 @@ def parse_kind(line: str):
# defined kind
try:
kind_str, trailing_line = parse_kind(trailing_line)
var_type += kind_str # XXX: see below
except ValueError:
return None
except TypeError: # XXX: remove with explicit kind specification in VarInfo
pass

# Class and Type statements need a kind spec
if not kind_str and var_type in ("TYPE", "CLASS"):
return None
# Make sure next character is space or comma or colon
if not kind_str and not trailing_line[0] in (" ", ",", ":"):
return None
#

keywords, trailing_line = parse_var_keywords(trailing_line)
# Check if this is a function definition
fun_def = read_fun_def(trailing_line, ResultSig(type=var_type, keywords=keywords))
fun_def = read_fun_def(
trailing_line,
ResultSig(type=var_type, keywords=keywords, kind=kind_str),
)
if fun_def or fun_only:
return fun_def
# Split the type and variable name
Expand All @@ -234,7 +234,12 @@ def parse_kind(line: str):
if var_words is None:
var_words = []

return "var", VarInfo(var_type, keywords, var_words, kind_str)
return "var", VarInfo(
var_type=var_type,
keywords=keywords,
var_names=var_words,
var_kind=kind_str,
)


def get_procedure_modifiers(
Expand Down Expand Up @@ -1411,6 +1416,7 @@ def parse(
desc,
keywords,
keyword_info=keyword_info,
proc_ptr=obj_info.var_kind,
link_obj=link_name,
)
else:
Expand All @@ -1421,7 +1427,7 @@ def parse(
desc,
keywords,
keyword_info=keyword_info,
# kind=obj_info.var_kind,
kind=obj_info.var_kind,
link_obj=link_name,
)
# If the object is fortran_var and a parameter include
Expand Down Expand Up @@ -1495,6 +1501,7 @@ def parse(
var_desc=obj_info.result.type,
keywords=keywords,
keyword_info=keyword_info,
kind=obj_info.result.kind,
)
file_ast.add_variable(new_obj)
log.debug("%s !!! FUNCTION - Ln:%d", line, line_no)
Expand Down
2 changes: 1 addition & 1 deletion fortls/regex_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class FortranRegularExpressions:
)
# Object regex patterns
CLASS_VAR: Pattern = compile(r"(TYPE|CLASS)[ ]*\(", I)
DEF_KIND: Pattern = compile(r"([a-z]*)[ ]*\((?:KIND|LEN)?[ =]*([a-z_]\w*)", I)
DEF_KIND: Pattern = compile(r"(\w*)[ ]*\((?:KIND|LEN)?[ =]*(\w*)", I)
OBJBREAK: Pattern = compile(r"[\/\-(.,+*<>=$: ]", I)


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ write_to = "fortls/_version.py"
profile = "black"

[tool.pytest.ini_options]
minversion = "7.0"
minversion = "7.2.0"
addopts = "-v --cov=fortls --cov-report=html --cov-report=xml --cov-context=test"
testpaths = ["fortls", "test"]
5 changes: 3 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ console_scripts =

[options.extras_require]
dev =
pytest >= 5.4.3
pytest-cov >= 2.12.1
pytest >= 7.2.0
pytest-cov >= 4.0.0
pytest-xdist >= 3.0.2
black
isort
pre-commit
Expand Down
2 changes: 1 addition & 1 deletion test/test_server_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def test_comp1():
string += comp_request(file_path, 21, 20)
string += comp_request(file_path, 21, 42)
string += comp_request(file_path, 23, 26)
errcode, results = run_request(string, ["--use_signature_help"])
errcode, results = run_request(string, ["--use_signature_help", "-n1"])
assert errcode == 0

exp_results = (
Expand Down
12 changes: 12 additions & 0 deletions test/test_server_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,15 @@ def test_keyword_arg_list_var_names():
errcode, results = run_request(string, ["-n", "1"])
assert errcode == 0
assert results[1]["diagnostics"] == []


def test_attribute_and_variable_name_collision():
"""Test variables named with attribute names do not cause a collision."""
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "diag")})
file_path = str(test_dir / "diag" / "var_shadowing_keyword_arg.f90")
string += write_rpc_notification(
"textDocument/didOpen", {"textDocument": {"uri": file_path}}
)
errcode, results = run_request(string, ["-n", "1"])
assert errcode == 0
assert results[1]["diagnostics"] == []
11 changes: 11 additions & 0 deletions test/test_source/diag/test_var_shadowing_keyword_arg.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module var_shadowing_keyword_arg
character(len=6), parameter :: TEST = "4.10.4"
character(len=6, kind=4), parameter :: TEST2 = "4.10.4"
real(kind=8) :: a
end module var_shadowing_keyword_arg

program program_var_shadowing_keyword_arg
use var_shadowing_keyword_arg
integer :: len
integer :: kind
end program program_var_shadowing_keyword_arg