2020 ATTRIBUTE_PROPERTY ,
2121 ATTRS_DECORATORS ,
2222 ATTRS_IMPORTS ,
23+ BINOP_OPERAND_PROPERTY ,
2324 NAME_RE ,
2425 TC001 ,
2526 TC002 ,
3031 TC007 ,
3132 TC008 ,
3233 TC009 ,
34+ TC010 ,
3335 TC100 ,
3436 TC101 ,
3537 TC200 ,
@@ -86,6 +88,8 @@ def visit(self, node: ast.AST) -> None:
8688 if isinstance (node , ast .BinOp ):
8789 if not isinstance (node .op , ast .BitOr ):
8890 return
91+ setattr (node .left , BINOP_OPERAND_PROPERTY , True )
92+ setattr (node .right , BINOP_OPERAND_PROPERTY , True )
8993 self .visit (node .left )
9094 self .visit (node .right )
9195 elif (py38 and isinstance (node , Index )) or isinstance (node , ast .Attribute ):
@@ -818,6 +822,9 @@ def __init__(self) -> None:
818822 #: All type annotations in the file, with quotes around them
819823 self .wrapped_annotations : list [WrappedAnnotation ] = []
820824
825+ #: All the invalid uses of string literals inside ast.BinOp
826+ self .invalid_binop_literals : list [ast .Constant ] = []
827+
821828 def visit (
822829 self , node : ast .AST , scope : Scope | None = None , type : Literal ['annotation' , 'alias' , 'new-alias' ] | None = None
823830 ) -> None :
@@ -836,13 +843,17 @@ def visit_annotation_name(self, node: ast.Name) -> None:
836843 )
837844
838845 def visit_annotation_string (self , node : ast .Constant ) -> None :
839- """Register wrapped annotation."""
846+ """Register wrapped annotation and invalid binop literals ."""
840847 setattr (node , ANNOTATION_PROPERTY , True )
841- self .wrapped_annotations .append (
842- WrappedAnnotation (
843- node .lineno , node .col_offset , node .value , set (NAME_RE .findall (node .value )), self .scope , self .type
848+ # we don't want to register them as both so we don't emit redundant errors
849+ if getattr (node , BINOP_OPERAND_PROPERTY , False ):
850+ self .invalid_binop_literals .append (node )
851+ else :
852+ self .wrapped_annotations .append (
853+ WrappedAnnotation (
854+ node .lineno , node .col_offset , node .value , set (NAME_RE .findall (node .value )), self .scope , self .type
855+ )
844856 )
845- )
846857
847858
848859class ImportVisitor (
@@ -958,6 +969,11 @@ def wrapped_annotations(self) -> list[WrappedAnnotation]:
958969 """All type annotations in the file, with quotes around them."""
959970 return self .annotation_visitor .wrapped_annotations
960971
972+ @property
973+ def invalid_binop_literals (self ) -> list [ast .Constant ]:
974+ """All invalid uses of binop literals."""
975+ return self .annotation_visitor .invalid_binop_literals
976+
961977 @property
962978 def typing_module_name (self ) -> str :
963979 """
@@ -1800,6 +1816,8 @@ def __init__(self, node: ast.Module, options: Optional[Namespace]) -> None:
18001816 self .empty_type_checking_blocks ,
18011817 # TC006
18021818 self .unquoted_type_in_cast ,
1819+ # TC010
1820+ self .invalid_string_literal_in_binop ,
18031821 # TC100, TC200, TC007
18041822 self .missing_quotes_or_futures_import ,
18051823 # TC101
@@ -1900,6 +1918,11 @@ def unquoted_type_in_cast(self) -> Flake8Generator:
19001918 for lineno , col_offset , annotation in self .visitor .unquoted_types_in_casts :
19011919 yield lineno , col_offset , TC006 .format (annotation = annotation ), None
19021920
1921+ def invalid_string_literal_in_binop (self ) -> Flake8Generator :
1922+ """TC010."""
1923+ for node in self .visitor .invalid_binop_literals :
1924+ yield node .lineno , node .col_offset , TC010 , None
1925+
19031926 def missing_quotes_or_futures_import (self ) -> Flake8Generator :
19041927 """TC100, TC200 and TC007."""
19051928 encountered_missing_quotes = False
0 commit comments