Skip to content
Draft
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
4 changes: 4 additions & 0 deletions pedal/assertions/syntactic.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self, code, at_least=1, root=None, **kwargs):
report = kwargs.get('report', MAIN_REPORT)
fields = {'code': code, 'at_least': at_least, 'capacity': '',
'root': root, 'code_message': report.format.python_expression(code)}
self.specifier = code
super().__init__(fields=fields, **kwargs)

def _check_usage(self, field_name, uses):
Expand All @@ -47,6 +48,7 @@ def __init__(self, code, at_most=0, root=None, **kwargs):
report = kwargs.get('report', MAIN_REPORT)
fields = {'code': code, 'at_most': at_most, 'capacity': '',
'root': root, 'code_message': report.format.python_expression(code)}
self.specifier = code
super().__init__(fields=fields, **kwargs)

def _check_usage(self, field_name, uses):
Expand Down Expand Up @@ -138,6 +140,7 @@ class reject_code_regex(RejectAssertionFeedback):
def __init__(self, pattern, at_most=0, flags=0, **kwargs):
fields = {'pattern': pattern, 'at_most': at_most, 'capacity': '',
'pattern_message': repr(pattern), 'flags': flags}
self.specifier = pattern
super(AssertionFeedback, self).__init__(fields=fields, **kwargs)

def condition(self):
Expand Down Expand Up @@ -192,6 +195,7 @@ class require_code_regex(RequireAssertionFeedback):
def __init__(self, pattern, at_least=1, flags=0, **kwargs):
fields = {'pattern': pattern, 'at_least': at_least, 'capacity': '',
'pattern_message': repr(pattern), 'flags': flags}
self.specifier = pattern
super(AssertionFeedback, self).__init__(fields=fields, **kwargs)

def condition(self):
Expand Down
30 changes: 30 additions & 0 deletions pedal/tifa/feedbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class action_after_return(TifaFeedback):
"*return variable was definitely set in this scope.")

def __init__(self, location, **kwargs):
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, **kwargs)


Expand All @@ -36,6 +37,7 @@ class return_outside_function(TifaFeedback):
justification = "TIFA visited a return node at the top level."

def __init__(self, location, **kwargs):
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, **kwargs)


Expand All @@ -49,6 +51,7 @@ class multiple_return_types(TifaFeedback):
"that unequal types.")

def __init__(self, location, expected, actual, **kwargs):
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, expected=expected,
actual=actual, **kwargs)

Expand All @@ -64,6 +67,7 @@ class write_out_of_scope(TifaFeedback):

def __init__(self, location, name, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
self.specifier = name
super().__init__(location=location, name=name,
name_message=report.format.name(name), **kwargs)

Expand All @@ -78,6 +82,7 @@ class unconnected_blocks(TifaFeedback):
justification = "TIFA found a name equal to ___"

def __init__(self, location, **kwargs):
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, **kwargs)


Expand All @@ -94,6 +99,7 @@ class iteration_problem(TifaFeedback):

def __init__(self, location, name, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
self.specifier = name
super().__init__(location=location, name=name,
name_message=report.format.name(name), **kwargs)

Expand All @@ -109,6 +115,7 @@ class initialization_problem(TifaFeedback):

def __init__(self, location, name, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
self.specifier = name
super().__init__(location=location, name=name,
name_message=report.format.name(name), **kwargs)

Expand All @@ -126,6 +133,7 @@ class possible_initialization_problem(TifaFeedback):

def __init__(self, location, name, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
self.specifier = name
super().__init__(location=location, name=name,
name_message=report.format.name(name), **kwargs)

Expand Down Expand Up @@ -154,6 +162,7 @@ def __init__(self, location, name, variable_type, **kwargs):
'kind': kind, 'initialization': initialization}
if 'fields' in kwargs:
fields.update(kwargs.pop('fields'))
self.specifier = name
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -170,6 +179,7 @@ class overwritten_variable(TifaFeedback):

def __init__(self, location, name, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
self.specifier = name
super().__init__(location=location, name=name,
name_message=report.format.name(name), **kwargs)

Expand All @@ -193,6 +203,7 @@ def __init__(self, location, iter_name, **kwargs):
fields = {'location': location, 'name': iter_name, 'iter': iter_list}
if 'fields' in kwargs:
fields.update(kwargs.pop('fields'))
self.specifier = iter_name if iter_name is not None else str(location)
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -212,6 +223,7 @@ def __init__(self, location, iter_name, **kwargs):
else:
iter_list = "variable " + report.format.name(iter_name)
fields = {'location': location, 'name': iter_name, 'iter': iter_list}
self.specifier = iter_name if iter_name is not None else str(location)
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -233,6 +245,7 @@ def __init__(self, location, operation, left, right, **kwargs):
'operation': operation, 'op_name': op_name,
'left': left, 'right': right,
'left_name': left_name, 'right_name': right_name}
self.specifier = op_name
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -253,6 +266,7 @@ def __init__(self, location, left, right, **kwargs):
fields = {'location': location,
'left': left, 'right': right,
'left_name': left_name, 'right_name': right_name}
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -278,6 +292,7 @@ def __init__(self, location, parameter_name, parameter, argument, **kwargs):
'argument_type': argument,
'parameter_type_name': parameter_type_name,
'argument_type_name': argument_type_name}
self.specifier = parameter_name
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -304,6 +319,7 @@ def __init__(self, location, field_name, field, argument, **kwargs):
'field_type_name': field_type_name,
'argument_type': argument,
'argument_type_name': argument_type_name}
self.specifier = field_name
super().__init__(location=location, fields=fields, **kwargs)

class read_out_of_scope(TifaFeedback):
Expand All @@ -318,6 +334,7 @@ class read_out_of_scope(TifaFeedback):

def __init__(self, location, name, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
self.specifier = name
super().__init__(location=location, name=name,
name_message=report.format.name(name), **kwargs)

Expand All @@ -336,6 +353,7 @@ def __init__(self, location, name, old, new, **kwargs):
fields = {'location': location, 'name': name,
'name_message': report.format.name(name),
'old': old, 'new': new}
self.specifier = name
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -352,6 +370,7 @@ def __init__(self, location, attr, old, new, **kwargs):
fields = {'location': location, 'attr': attr,
'attr_message': report.format.name(attr),
'old': old, 'new': new}
self.specifier = attr
super().__init__(location=location, fields=fields, **kwargs)

class type_change_append(TifaFeedback):
Expand All @@ -364,6 +383,7 @@ class type_change_append(TifaFeedback):

def __init__(self, location, old, new, **kwargs):
fields = {'location': location, 'old': old, 'new': new}
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -376,6 +396,7 @@ class unnecessary_second_branch(TifaFeedback):
justification = "There is an else or if statement who's body is just pass."

def __init__(self, location, **kwargs):
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, **kwargs)


Expand All @@ -386,6 +407,7 @@ class else_on_loop_body(TifaFeedback):
justification = ""

def __init__(self, location, **kwargs):
self.specifier = str(location) if location is not None else ""
super().__init__(location=location, **kwargs)


Expand All @@ -397,6 +419,8 @@ class recursive_call(TifaFeedback):
muted = True

def __init__(self, location, name, **kwargs):
# name may be a State object with a .name attribute, or a plain string
self.specifier = name.name if hasattr(name, 'name') else name
super().__init__(location=location, name=name, **kwargs)


Expand All @@ -416,6 +440,7 @@ def __init__(self, location, name, called_type, **kwargs):
fields = {'location': location, 'name': name,
'called_type': called_type,
'singular_name': singular_name}
self.specifier = name
super().__init__(fields=fields, **kwargs)


Expand All @@ -430,6 +455,7 @@ class incorrect_arity(TifaFeedback):
def __init__(self, location, function_name, expected_count, actual_count, is_constructor=False, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
function_type = 'constructor function' if is_constructor else 'function'
self.specifier = function_name
super().__init__(location=location, function_name=function_name,
expected_count=expected_count, actual_count=actual_count,
function_name_message=report.format.name(function_name),
Expand All @@ -447,6 +473,7 @@ class module_not_found(TifaFeedback):
def __init__(self, location, name, is_dynamic=False, error=None, **kwargs):
fields = {"location": location, "name": name,
"is_dynamic": is_dynamic, "error": error}
self.specifier = name
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -460,6 +487,7 @@ class append_to_non_list(TifaFeedback):
def __init__(self, location, name, actual_type, **kwargs):
fields = {'location': location, "name": name,
"actual_type": actual_type}
self.specifier = name
super().__init__(location=location, fields=fields, **kwargs)


Expand All @@ -477,6 +505,7 @@ class nested_function_definition(TifaFeedback):

def __init__(self, location, name, **kwargs):
report = kwargs.get("report", MAIN_REPORT)
self.specifier = name
super().__init__(location=location, name=name,
name_message=report.format.name(name), **kwargs)

Expand All @@ -497,6 +526,7 @@ def __init__(self, location, name, call_type, result_type, **kwargs):
fields = {'location': location, 'name': name, 'call_type': call_type,
'result_type': result_type,
'name_message': report.format.name(name)}
self.specifier = name
super().__init__(fields=fields, location=location, **kwargs)


Expand Down
101 changes: 101 additions & 0 deletions tests/test_tifa.py
Original file line number Diff line number Diff line change
Expand Up @@ -1539,5 +1539,106 @@ def foo() -> int:
self.assertEqual(4,
result.issues['unused_variable'][0].location.line)

class TestSpecifiers(unittest.TestCase):
"""Tests to verify that TIFA feedback functions set their specifier correctly."""

def test_unused_variable_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('a = 0')
issues = result.issues
self.assertEqual(issues['unused_variable'][0].specifier, 'a')

def test_initialization_problem_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('print(my_var)')
issues = result.issues
self.assertEqual(issues['initialization_problem'][0].specifier, 'my_var')

def test_possible_initialization_problem_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('if True:\n x = 0\nprint(x)')
issues = result.issues
self.assertEqual(issues['possible_initialization_problem'][0].specifier, 'x')

def test_overwritten_variable_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('a = 0\na = 5')
issues = result.issues
self.assertEqual(issues['overwritten_variable'][0].specifier, 'a')

def test_write_out_of_scope_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('counter = 0\ndef increment():\n counter = counter + 1\nincrement()')
issues = result.issues
# write_out_of_scope triggered when assigning to outer scope variable
if 'write_out_of_scope' in issues:
self.assertEqual(issues['write_out_of_scope'][0].specifier, 'counter')

def test_iteration_problem_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('for i in i:\n pass')
issues = result.issues
if 'iteration_problem' in issues:
self.assertEqual(issues['iteration_problem'][0].specifier, 'i')

def test_parameter_type_mismatch_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('def f(x: int):\n return x\nf("hello")')
issues = result.issues
self.assertEqual(issues['parameter_type_mismatch'][0].specifier, 'x')

def test_recursive_call_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('def my_func():\n my_func()\nmy_func()')
issues = result.issues
self.assertEqual(issues['recursive_call'][0].specifier, 'my_func')

def test_nested_function_definition_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('if False:\n def inner_func():\n pass')
issues = result.issues
self.assertEqual(issues['nested_function_definition'][0].specifier, 'inner_func')

def test_action_after_return_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('def x():\n return 5\n return 4\nx()')
issues = result.issues
self.assertIsNotNone(issues['action_after_return'][0].specifier)
self.assertNotEqual(issues['action_after_return'][0].specifier, "")

def test_incompatible_types_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('1 + "hello"')
issues = result.issues
self.assertIsNotNone(issues['incompatible_types'][0].specifier)
self.assertNotEqual(issues['incompatible_types'][0].specifier, "")

def test_module_not_found_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('import nonexistent_module_xyz')
issues = result.issues
if 'module_not_found' in issues:
self.assertEqual(issues['module_not_found'][0].specifier, 'nonexistent_module_xyz')

def test_read_out_of_scope_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('def x(param):\n return param\nx(0)\nparam')
issues = result.issues
self.assertEqual(issues['read_out_of_scope'][0].specifier, 'param')

def test_unused_returned_value_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('abs(-5)')
issues = result.issues
self.assertEqual(issues['unused_returned_value'][0].specifier, 'abs')

def test_incorrect_arity_specifier(self):
tifa = pedal.tifa.Tifa()
result = tifa.process_code('def f(x): return x\nf(1, 2)')
issues = result.issues
if 'incorrect_arity' in issues:
self.assertEqual(issues['incorrect_arity'][0].specifier, 'f')


if __name__ == '__main__':
unittest.main(buffer=False)