Compare commits
3
Commits
95b218fbed
...
c29c0ed3ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c29c0ed3ec
|
||
|
|
fa7e56cb77
|
||
|
|
13c19db818
|
No files matched your search
+28
-11
@@ -311,12 +311,12 @@ class Checker(
|
|||||||
}
|
}
|
||||||
set_args: set[str] = set()
|
set_args: set[str] = set()
|
||||||
|
|
||||||
required_positional: set[str] = {
|
required_positional: list[str] = [
|
||||||
arg.name for arg in function.pos_args + function.args if arg.required
|
arg.name for arg in function.pos_args + function.args if arg.required
|
||||||
}
|
]
|
||||||
required_keyword: set[str] = {
|
required_keyword: list[str] = [
|
||||||
arg.name for arg in function.kw_args if arg.required
|
arg.name for arg in function.kw_args if arg.required
|
||||||
}
|
]
|
||||||
|
|
||||||
mapped: list[MappedArgument] = []
|
mapped: list[MappedArgument] = []
|
||||||
|
|
||||||
@@ -336,9 +336,12 @@ class Checker(
|
|||||||
else:
|
else:
|
||||||
self.error(arg[0].location, "Too many positional arguments")
|
self.error(arg[0].location, "Too many positional arguments")
|
||||||
break
|
break
|
||||||
required_positional.discard(param.name)
|
name: str = param.name
|
||||||
required_keyword.discard(param.name)
|
if name in required_positional:
|
||||||
set_args.add(param.name)
|
required_positional.remove(name)
|
||||||
|
if name in required_keyword:
|
||||||
|
required_keyword.remove(name)
|
||||||
|
set_args.add(name)
|
||||||
mapped.append(
|
mapped.append(
|
||||||
MappedArgument(
|
MappedArgument(
|
||||||
expr=arg[0],
|
expr=arg[0],
|
||||||
@@ -359,8 +362,10 @@ class Checker(
|
|||||||
self.error(arg[0].location, f"Unknown keyword argument '{name}'")
|
self.error(arg[0].location, f"Unknown keyword argument '{name}'")
|
||||||
continue
|
continue
|
||||||
param = kw_params.pop(name)
|
param = kw_params.pop(name)
|
||||||
required_positional.discard(name)
|
if name in required_positional:
|
||||||
required_keyword.discard(name)
|
required_positional.remove(name)
|
||||||
|
if name in required_keyword:
|
||||||
|
required_keyword.remove(name)
|
||||||
set_args.add(name)
|
set_args.add(name)
|
||||||
mapped.append(
|
mapped.append(
|
||||||
MappedArgument(
|
MappedArgument(
|
||||||
@@ -370,16 +375,28 @@ class Checker(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def join_args(args: list[str]) -> str:
|
||||||
|
args = list(map(lambda a: f"'{a}'", args))
|
||||||
|
if len(args) == 0:
|
||||||
|
return ""
|
||||||
|
if len(args) == 1:
|
||||||
|
return args[0]
|
||||||
|
return ", ".join(args[:-1]) + " and " + args[-1]
|
||||||
|
|
||||||
if len(required_positional) != 0:
|
if len(required_positional) != 0:
|
||||||
|
plural: str = "" if len(required_positional) == 1 else "s"
|
||||||
|
args: str = join_args(required_positional)
|
||||||
self.error(
|
self.error(
|
||||||
call.location,
|
call.location,
|
||||||
f"Missing required positional arguments: {required_positional}",
|
f"Missing required positional argument{plural}: {args}",
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(required_keyword) != 0:
|
if len(required_keyword) != 0:
|
||||||
|
plural: str = "" if len(required_keyword) == 1 else "s"
|
||||||
|
args: str = join_args(required_keyword)
|
||||||
self.error(
|
self.error(
|
||||||
call.location,
|
call.location,
|
||||||
f"Missing required keyword arguments: {required_keyword}",
|
f"Missing required keyword argument{plural}: {args}",
|
||||||
)
|
)
|
||||||
|
|
||||||
return mapped
|
return mapped
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# type: ignore
|
||||||
|
# ruff: disable[F821]
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
df: Frame[
|
||||||
|
verified: bool,
|
||||||
|
birth_year: int,
|
||||||
|
height: float + ( _ > 0 ) + ( _ < 250 ),
|
||||||
|
name: str,
|
||||||
|
date: datetime,
|
||||||
|
float,
|
||||||
|
unknown: _,
|
||||||
|
_
|
||||||
|
]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"diagnostics": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
a: int = 3
|
||||||
|
b: int = 4
|
||||||
|
|
||||||
|
c = a + b
|
||||||
|
|
||||||
|
c = "invalid"
|
||||||
|
|
||||||
|
d = True
|
||||||
|
e = d + d
|
||||||
|
|
||||||
|
f: float = a
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
6,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
6,
|
||||||
|
13
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Cannot assign BaseType(name='str') to c of type BaseType(name='int')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
9,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
9,
|
||||||
|
9
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Undefined operation __add__ between BaseType(name='bool') and BaseType(name='bool')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
11,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
11,
|
||||||
|
12
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Cannot assign BaseType(name='int') to f of type BaseType(name='float')"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
def foo(a: int, /, b: float, *, c: str):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
r1 = foo()
|
||||||
|
r2 = foo(1)
|
||||||
|
r3 = foo(1, 2.0)
|
||||||
|
r4 = foo(1, b=2.0)
|
||||||
|
r5 = foo(1, 2.0, "test")
|
||||||
|
r6 = foo(1, 2.0, b=3.0)
|
||||||
|
r7 = foo(a=1)
|
||||||
|
r8 = foo(g="test")
|
||||||
|
|
||||||
|
r9a = foo(1, 2.0, c="test")
|
||||||
|
r9b = foo(1, b=2.0, c="test")
|
||||||
|
r9c = foo(1, c="test", b=2.0)
|
||||||
|
|
||||||
|
r10 = foo("a", 3, c=False)
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
{
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
5,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
5,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required positional arguments: 'a' and 'b'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
5,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
5,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
6,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
6,
|
||||||
|
11
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required positional argument: 'b'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
6,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
6,
|
||||||
|
11
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
7,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
7,
|
||||||
|
16
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
8,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
8,
|
||||||
|
18
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
9,
|
||||||
|
17
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
9,
|
||||||
|
23
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Too many positional arguments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
9,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
9,
|
||||||
|
24
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
10,
|
||||||
|
19
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
10,
|
||||||
|
22
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Multiple values for argument 'b'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
10,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
10,
|
||||||
|
23
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
11,
|
||||||
|
11
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
11,
|
||||||
|
12
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Unknown keyword argument 'a'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
11,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
11,
|
||||||
|
13
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required positional arguments: 'a' and 'b'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
11,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
11,
|
||||||
|
13
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
12,
|
||||||
|
11
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
12,
|
||||||
|
17
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Unknown keyword argument 'g'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
12,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
12,
|
||||||
|
18
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required positional arguments: 'a' and 'b'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
12,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
12,
|
||||||
|
18
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Missing required keyword argument: 'c'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
18,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
18,
|
||||||
|
13
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Wrong type for argument 'a', expected BaseType(name='int'), got BaseType(name='str')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
18,
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
18,
|
||||||
|
16
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Wrong type for argument 'b', expected BaseType(name='float'), got BaseType(name='int')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
18,
|
||||||
|
20
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
18,
|
||||||
|
25
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Wrong type for argument 'c', expected BaseType(name='str'), got BaseType(name='bool')"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
type Meter(float)
|
||||||
|
type Second(float)
|
||||||
|
type MeterPerSecond(float)
|
||||||
|
|
||||||
|
extend Meter {
|
||||||
|
op __add__(Meter) -> Meter
|
||||||
|
op __sub__(Meter) -> Meter
|
||||||
|
op __truediv__(Second) -> MeterPerSecond
|
||||||
|
}
|
||||||
|
|
||||||
|
extend Second {
|
||||||
|
op __add__(Second) -> Second
|
||||||
|
op __sub__(Second) -> Second
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# type: ignore
|
||||||
|
# ruff: disable [F821]
|
||||||
|
|
||||||
|
midas.using("04_custom_types.midas")
|
||||||
|
|
||||||
|
distance: Meter = 123.45
|
||||||
|
time: Second = 6.7
|
||||||
|
speed = distance / time
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
6,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
6,
|
||||||
|
24
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Cannot assign BaseType(name='float') to distance of type SimpleType(name='Meter', base=BaseType(name='float'))"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Error",
|
||||||
|
"location": {
|
||||||
|
"start": [
|
||||||
|
7,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"end": [
|
||||||
|
7,
|
||||||
|
18
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"message": "Cannot assign BaseType(name='float') to time of type SimpleType(name='Second', base=BaseType(name='float'))"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import ast
|
||||||
|
import json
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import midas.ast.python as p
|
||||||
|
from midas.checker.checker import Checker
|
||||||
|
from midas.checker.diagnostic import Diagnostic
|
||||||
|
from midas.parser.python import PythonParser
|
||||||
|
from midas.resolver.resolver import Resolver
|
||||||
|
from tests.base import Tester
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CaseResult:
|
||||||
|
diagnostics: list[dict] = field(default_factory=list)
|
||||||
|
|
||||||
|
def dumps(self) -> str:
|
||||||
|
return json.dumps(asdict(self), indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
class CheckerTester(Tester):
|
||||||
|
@property
|
||||||
|
def namespace(self) -> str:
|
||||||
|
return "checker"
|
||||||
|
|
||||||
|
def _list_tests(self) -> list[Path]:
|
||||||
|
return list(self.base_dir.rglob("*.py"))
|
||||||
|
|
||||||
|
def _exec_case(self, path: Path) -> CaseResult:
|
||||||
|
if not path.exists():
|
||||||
|
raise FileNotFoundError(f"Could not find test '{path}'")
|
||||||
|
if not path.is_file():
|
||||||
|
raise TypeError(f"Test '{path}' is not a file")
|
||||||
|
|
||||||
|
source: str = path.read_text()
|
||||||
|
tree: ast.Module = ast.parse(source, filename=path)
|
||||||
|
parser = PythonParser()
|
||||||
|
stmts: list[p.Stmt] = parser.parse_module(tree)
|
||||||
|
resolver = Resolver()
|
||||||
|
resolver.resolve(*stmts)
|
||||||
|
result: CaseResult = CaseResult()
|
||||||
|
checker = Checker(resolver.locals, file_path=path)
|
||||||
|
diagnostics: list[Diagnostic] = checker.check(stmts)
|
||||||
|
for diagnostic in diagnostics:
|
||||||
|
result.diagnostics.append(
|
||||||
|
{
|
||||||
|
"type": str(diagnostic.type),
|
||||||
|
"location": {
|
||||||
|
"start": (
|
||||||
|
diagnostic.location.lineno,
|
||||||
|
diagnostic.location.col_offset,
|
||||||
|
),
|
||||||
|
"end": (
|
||||||
|
diagnostic.location.end_lineno,
|
||||||
|
diagnostic.location.end_col_offset,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"message": diagnostic.message,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
CheckerTester.main()
|
||||||
Reference in new issue
Block a user