diff --git a/src/api/check.py b/src/api/check.py index 7d252c49a..d27aaf56c 100644 --- a/src/api/check.py +++ b/src/api/check.py @@ -91,7 +91,7 @@ def check_type_is_explicit(lineno: int, id_: str, type_): errmsg.syntax_error_undeclared_type(lineno, id_) -def check_call_arguments(lineno: int, id_: str, args): +def check_call_arguments(lineno: int, id_: str, args, filename: str): """Check arguments against function signature. Checks every argument in a function call against a function. @@ -109,14 +109,14 @@ def check_call_arguments(lineno: int, id_: str, args): param_names = {x.name for x in entry.ref.params} for arg in args: if arg.name is not None and arg.name not in param_names: - errmsg.error(lineno, f"Unexpected argument '{arg.name}'", fname=entry.filename) + errmsg.error(lineno, f"Unexpected argument '{arg.name}'", fname=filename) return False last_arg_name = None for arg, param in zip(args, entry.ref.params): if last_arg_name is not None and arg.name is None: errmsg.error( - lineno, f"Positional argument cannot go after keyword argument '{last_arg_name}'", fname=entry.filename + lineno, f"Positional argument cannot go after keyword argument '{last_arg_name}'", fname=filename ) return False @@ -139,15 +139,13 @@ def check_call_arguments(lineno: int, id_: str, args): for arg in args: if arg.name is None: - errmsg.error(lineno, f"Too many arguments for Function '{id_}'", fname=entry.filename) + errmsg.error(lineno, f"Too many arguments for Function '{id_}'", fname=filename) return False if len(named_args) != len(entry.ref.params): c = "s" if len(entry.ref.params) != 1 else "" errmsg.error( - lineno, - f"Function '{id_}' takes {len(entry.ref.params)} parameter{c}, not {len(args)}", - fname=entry.filename, + lineno, f"Function '{id_}' takes {len(entry.ref.params)} parameter{c}, not {len(args)}", fname=filename ) return False @@ -195,8 +193,8 @@ def check_pending_calls(): result = True # Check for functions defined after calls (parameters, etc) - for id_, params, lineno in global_.FUNCTION_CALLS: - result = result and check_call_arguments(lineno, id_, params) + for call in global_.FUNCTION_CALLS: + result = result and check_call_arguments(call.lineno, call.entry.original_name, call.args, call.filename) return result diff --git a/src/api/global_.py b/src/api/global_.py index 03dd03f22..01856fa5a 100644 --- a/src/api/global_.py +++ b/src/api/global_.py @@ -14,6 +14,7 @@ from src.api.opcodestemps import OpcodesTemps if TYPE_CHECKING: + from src.symbols.call import SymbolCALL from src.symbols.id_ import SymbolID @@ -90,7 +91,7 @@ class LoopInfo(NamedTuple): # Function calls pending to check # Each scope pushes (prepends) an empty list # ---------------------------------------------------------------------- -FUNCTION_CALLS: list[SymbolID] = [] +FUNCTION_CALLS: list[SymbolCALL] = [] # ---------------------------------------------------------------------- # Function level entry ID in which scope we are in. If the list diff --git a/src/symbols/call.py b/src/symbols/call.py index aebc1d4b1..ff7d9f13a 100644 --- a/src/symbols/call.py +++ b/src/symbols/call.py @@ -40,8 +40,8 @@ def __init__(self, entry: SymbolID, arglist: Iterable[SymbolARGUMENT], lineno: i super().__init__() self.entry = entry self.args = arglist # Func. call / array access - self.lineno = lineno - self.filename = filename + self.lineno: int = lineno + self.filename: str = filename ref = entry.ref if isinstance(ref, FuncRef): @@ -95,17 +95,13 @@ def make_node(cls, id_: str, params, lineno: int, filename: str) -> Optional["Sy return None if entry.declared and not entry.forwarded: - check.check_call_arguments(lineno, id_, params) + check.check_call_arguments(lineno, id_, params, filename) else: # All functions goes to global scope by default if entry.token != "FUNCTION": entry = entry.to_function(lineno) gl.SYMBOL_TABLE.move_to_global_scope(id_) - gl.FUNCTION_CALLS.append( - ( - id_, - params, - lineno, - ) - ) + result = cls(entry, params, lineno, filename) + gl.FUNCTION_CALLS.append(result) + return result return cls(entry, params, lineno, filename) diff --git a/src/symbols/id_/_id.py b/src/symbols/id_/_id.py index 93679fad7..623d414f0 100644 --- a/src/symbols/id_/_id.py +++ b/src/symbols/id_/_id.py @@ -33,6 +33,7 @@ class SymbolID(SymbolIdABC): __slots__ = ( "name", + "original_name", "filename", "lineno", "mangled", @@ -58,7 +59,8 @@ def __init__( super().__init__(name=name, lineno=lineno, filename=filename, type_=type_, class_=class_) assert class_ in (CLASS.const, CLASS.label, CLASS.var, CLASS.unknown) - self.name = name + self.name = name # This value will be modified later removing the trailing sigil ($) if used. + self.original_name = name # This value will always contain the original name, preserving the sigil if used self.filename = global_.FILENAME if filename is None else filename # In which file was first used self.lineno = lineno # In which line was first used self.mangled = f"{global_.MANGLE_CHR}{name}" # This value will be overridden later diff --git a/tests/functional/arch/zx48k/bad_fname_err5.bas b/tests/functional/arch/zx48k/bad_fname_err5.bas new file mode 100644 index 000000000..060ff96ff --- /dev/null +++ b/tests/functional/arch/zx48k/bad_fname_err5.bas @@ -0,0 +1,10 @@ +#line 1 "file2.bas" +#line 1 "file1.bas" +sub foo(a as ubyte, b as ubyte) + print a, ", ", b +end sub + +#line 2 "file2.bas" + + +foo(42) diff --git a/tests/functional/arch/zx48k/bad_fname_err6.bas b/tests/functional/arch/zx48k/bad_fname_err6.bas new file mode 100644 index 000000000..d3dba4345 --- /dev/null +++ b/tests/functional/arch/zx48k/bad_fname_err6.bas @@ -0,0 +1,10 @@ +#line 1 "file2.bas" +#line 1 "file1.bas" +sub foo(a as ubyte, b as ubyte) + print a, ", ", b +end sub + +#line 2 "file2.bas" + + +foo(42, 43, 44) diff --git a/tests/functional/cmdline/test_errmsg.txt b/tests/functional/cmdline/test_errmsg.txt index 49a228bac..04c984727 100644 --- a/tests/functional/cmdline/test_errmsg.txt +++ b/tests/functional/cmdline/test_errmsg.txt @@ -159,6 +159,10 @@ ND.Controls.bas:4: error: Invalid argument 'dirData' ND.Controls.bas:2: warning: [W150] Variable 'dirData' is never used >>> process_file('arch/zx48k/bad_fname_err4.bas', ['-S', '-q']) ND.Controls.bas:2: error: sub 'Controls_LABEL' declared but not implemented +>>> process_file('arch/zx48k/bad_fname_err5.bas', ['-S', '-q']) +file2.bas:4: error: Function 'foo' takes 2 parameters, not 1 +>>> process_file('arch/zx48k/bad_fname_err6.bas', ['-S', '-q']) +file2.bas:4: error: Too many arguments for Function 'foo' # DO LOOP type errors >>> process_file('arch/zx48k/do_crash.bas', ['-S', '-q'])