Skip to content

Commit 86bc0a2

Browse files
committed
refactor: addresss type hints in query_lang
1 parent 5a1ae0d commit 86bc0a2

File tree

4 files changed

+53
-13
lines changed

4 files changed

+53
-13
lines changed

src/tagstudio/core/query_lang/ast.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
# Copyright (C) 2025
2+
# Licensed under the GPL-3.0 License.
3+
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
4+
5+
16
from abc import ABC, abstractmethod
27
from enum import Enum
3-
from typing import Generic, TypeVar, Union
8+
from typing import Generic, TypeVar, override
49

510

611
class ConstraintType(Enum):
@@ -12,7 +17,7 @@ class ConstraintType(Enum):
1217
Special = 5
1318

1419
@staticmethod
15-
def from_string(text: str) -> Union["ConstraintType", None]:
20+
def from_string(text: str) -> "ConstraintType | None":
1621
return {
1722
"tag": ConstraintType.Tag,
1823
"tag_id": ConstraintType.TagID,
@@ -24,14 +29,16 @@ def from_string(text: str) -> Union["ConstraintType", None]:
2429

2530

2631
class AST:
27-
parent: Union["AST", None] = None
32+
parent: "AST | None" = None
2833

34+
@override
2935
def __str__(self):
3036
class_name = self.__class__.__name__
3137
fields = vars(self) # Get all instance variables as a dictionary
3238
field_str = ", ".join(f"{key}={value}" for key, value in fields.items())
3339
return f"{class_name}({field_str})"
3440

41+
@override
3542
def __repr__(self) -> str:
3643
return self.__str__()
3744

src/tagstudio/core/query_lang/parser.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Copyright (C) 2025
2+
# Licensed under the GPL-3.0 License.
3+
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
4+
5+
16
from tagstudio.core.query_lang.ast import (
27
AST,
38
ANDList,
@@ -27,7 +32,7 @@ def parse(self) -> AST:
2732
if self.next_token.type == TokenType.EOF:
2833
return ORList([])
2934
out = self.__or_list()
30-
if self.next_token.type != TokenType.EOF:
35+
if self.next_token.type != TokenType.EOF: # pyright: ignore[reportUnnecessaryComparison]
3136
raise ParsingError(self.next_token.start, self.next_token.end, "Syntax Error")
3237
return out
3338

@@ -41,7 +46,7 @@ def __or_list(self) -> AST:
4146
return ORList(terms) if len(terms) > 1 else terms[0]
4247

4348
def __is_next_or(self) -> bool:
44-
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "OR"
49+
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "OR" # pyright: ignore
4550

4651
def __and_list(self) -> AST:
4752
elements = [self.__term()]
@@ -67,7 +72,7 @@ def __skip_and(self) -> None:
6772
raise self.__syntax_error("Unexpected AND")
6873

6974
def __is_next_and(self) -> bool:
70-
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "AND"
75+
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "AND" # pyright: ignore
7176

7277
def __term(self) -> AST:
7378
if self.__is_next_not():
@@ -85,11 +90,14 @@ def __term(self) -> AST:
8590
return self.__constraint()
8691

8792
def __is_next_not(self) -> bool:
88-
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "NOT"
93+
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "NOT" # pyright: ignore
8994

9095
def __constraint(self) -> Constraint:
9196
if self.next_token.type == TokenType.CONSTRAINTTYPE:
92-
self.last_constraint_type = self.__eat(TokenType.CONSTRAINTTYPE).value
97+
constraint = self.__eat(TokenType.CONSTRAINTTYPE).value
98+
if not isinstance(constraint, ConstraintType):
99+
raise self.__syntax_error()
100+
self.last_constraint_type = constraint
93101

94102
value = self.__literal()
95103

@@ -98,7 +106,7 @@ def __constraint(self) -> Constraint:
98106
self.__eat(TokenType.SBRACKETO)
99107
properties.append(self.__property())
100108

101-
while self.next_token.type == TokenType.COMMA:
109+
while self.next_token.type == TokenType.COMMA: # pyright: ignore[reportUnnecessaryComparison]
102110
self.__eat(TokenType.COMMA)
103111
properties.append(self.__property())
104112

@@ -110,11 +118,16 @@ def __property(self) -> Property:
110118
key = self.__eat(TokenType.ULITERAL).value
111119
self.__eat(TokenType.EQUALS)
112120
value = self.__literal()
121+
if not isinstance(key, str):
122+
raise self.__syntax_error()
113123
return Property(key, value)
114124

115125
def __literal(self) -> str:
116126
if self.next_token.type in [TokenType.QLITERAL, TokenType.ULITERAL]:
117-
return self.__eat(self.next_token.type).value
127+
literal = self.__eat(self.next_token.type).value
128+
if not isinstance(literal, str):
129+
raise self.__syntax_error()
130+
return literal
118131
raise self.__syntax_error()
119132

120133
def __eat(self, type: TokenType) -> Token:

src/tagstudio/core/query_lang/tokenizer.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
# Copyright (C) 2025
2+
# Licensed under the GPL-3.0 License.
3+
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
4+
5+
16
from enum import Enum
2-
from typing import Any
7+
from typing import override
38

49
from tagstudio.core.query_lang.ast import ConstraintType
510
from tagstudio.core.query_lang.util import ParsingError
@@ -21,12 +26,14 @@ class TokenType(Enum):
2126

2227
class Token:
2328
type: TokenType
24-
value: Any
29+
value: str | ConstraintType | None
2530

2631
start: int
2732
end: int
2833

29-
def __init__(self, type: TokenType, value: Any, start: int, end: int) -> None:
34+
def __init__(
35+
self, type: TokenType, value: str | ConstraintType | None, start: int, end: int
36+
) -> None:
3037
self.type = type
3138
self.value = value
3239
self.start = start
@@ -40,9 +47,11 @@ def from_type(type: TokenType, pos: int) -> "Token":
4047
def EOF(pos: int) -> "Token": # noqa: N802
4148
return Token.from_type(TokenType.EOF, pos)
4249

50+
@override
4351
def __str__(self) -> str:
4452
return f"Token({self.type}, {self.value}, {self.start}, {self.end})" # pragma: nocover
4553

54+
@override
4655
def __repr__(self) -> str:
4756
return self.__str__() # pragma: nocover
4857

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
1+
# Copyright (C) 2025
2+
# Licensed under the GPL-3.0 License.
3+
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
4+
5+
6+
from typing import override
7+
8+
19
class ParsingError(BaseException):
210
start: int
311
end: int
412
msg: str
513

614
def __init__(self, start: int, end: int, msg: str = "Syntax Error") -> None:
15+
super().__init__()
716
self.start = start
817
self.end = end
918
self.msg = msg
1019

20+
@override
1121
def __str__(self) -> str:
1222
return f"Syntax Error {self.start}->{self.end}: {self.msg}" # pragma: nocover
1323

24+
@override
1425
def __repr__(self) -> str:
1526
return self.__str__() # pragma: nocover

0 commit comments

Comments
 (0)