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()
|
||||
|
||||
required_positional: set[str] = {
|
||||
required_positional: list[str] = [
|
||||
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
|
||||
}
|
||||
]
|
||||
|
||||
mapped: list[MappedArgument] = []
|
||||
|
||||
@@ -336,9 +336,12 @@ class Checker(
|
||||
else:
|
||||
self.error(arg[0].location, "Too many positional arguments")
|
||||
break
|
||||
required_positional.discard(param.name)
|
||||
required_keyword.discard(param.name)
|
||||
set_args.add(param.name)
|
||||
name: str = param.name
|
||||
if name in required_positional:
|
||||
required_positional.remove(name)
|
||||
if name in required_keyword:
|
||||
required_keyword.remove(name)
|
||||
set_args.add(name)
|
||||
mapped.append(
|
||||
MappedArgument(
|
||||
expr=arg[0],
|
||||
@@ -359,8 +362,10 @@ class Checker(
|
||||
self.error(arg[0].location, f"Unknown keyword argument '{name}'")
|
||||
continue
|
||||
param = kw_params.pop(name)
|
||||
required_positional.discard(name)
|
||||
required_keyword.discard(name)
|
||||
if name in required_positional:
|
||||
required_positional.remove(name)
|
||||
if name in required_keyword:
|
||||
required_keyword.remove(name)
|
||||
set_args.add(name)
|
||||
mapped.append(
|
||||
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:
|
||||
plural: str = "" if len(required_positional) == 1 else "s"
|
||||
args: str = join_args(required_positional)
|
||||
self.error(
|
||||
call.location,
|
||||
f"Missing required positional arguments: {required_positional}",
|
||||
f"Missing required positional argument{plural}: {args}",
|
||||
)
|
||||
|
||||
if len(required_keyword) != 0:
|
||||
plural: str = "" if len(required_keyword) == 1 else "s"
|
||||
args: str = join_args(required_keyword)
|
||||
self.error(
|
||||
call.location,
|
||||
f"Missing required keyword arguments: {required_keyword}",
|
||||
f"Missing required keyword argument{plural}: {args}",
|
||||
)
|
||||
|
||||
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