11# -*- coding: utf-8 -*-
2- __all__ = ('FileLine' , 'FileLineSet' )
2+ __all__ = ('FileLine' , 'FileLineSet' , 'FileLineMap' )
33
44import typing
55import collections .abc
66from typing import (Dict , List , Set , Iterator , Iterable , Any , FrozenSet ,
7- Callable , Optional , Tuple )
7+ Callable , Optional , Tuple , TypeVar , MutableMapping ,
8+ Mapping )
89
10+ from deprecated import deprecated
911import attr
1012
13+ T = TypeVar ('T' )
14+
1115
1216@attr .s (frozen = True , slots = True )
1317class FileLine :
@@ -22,6 +26,7 @@ def from_string(s: str) -> 'FileLine':
2226 return FileLine (fn , num )
2327
2428 @staticmethod
29+ @deprecated (version = '2.1.29' , reason = 'Replaced by FileLineMap.' )
2530 def compactify (d : Dict ['FileLine' , Any ]) -> Dict [str , Dict [int , Any ]]:
2631 """
2732 Converts a dictionary that is indexed by FileLine objects into a
@@ -39,6 +44,7 @@ def compactify(d: Dict['FileLine', Any]) -> Dict[str, Dict[int, Any]]:
3944 return out
4045
4146 @staticmethod
47+ @deprecated (version = '2.1.29' , reason = 'Replaced by FileLineMap.' )
4248 def decompactify (d : Dict [str , Dict [int , Any ]]) -> 'Dict[FileLine, Any]' :
4349 lines = {} # type: Dict['FileLine', Any]
4450 for fn in d :
@@ -53,8 +59,46 @@ def __str__(self) -> str:
5359# see: https://github.com/python/mypy/issues/5446
5460if typing .TYPE_CHECKING :
5561 BaseSet = Set [FileLine ]
62+ BaseMap = MutableMapping [FileLine , T ]
5663else :
5764 BaseSet = collections .abc .Set
65+ BaseMap = collections .abc .MutableMapping
66+
67+
68+ class FileLineMap (BaseMap ):
69+ """
70+ An efficient implementation of maps indexed by file lines.
71+ Note that operations on instances of this class are NOT thread safe.
72+ """
73+ def __init__ (self , contents : Mapping [FileLine , T ]) -> None :
74+ self .__contents = {} # type: Dict[str, Dict[int, T]]
75+ self .__length = 0
76+ for line , val in contents .items ():
77+ self [line ] = val
78+
79+ def __iter__ (self ) -> Iterator [FileLine ]:
80+ for fn in self .__contents :
81+ for lineno in self .__contents [fn ]:
82+ yield FileLine (fn , lineno )
83+
84+ def __len__ (self ) -> int :
85+ return self .__length
86+
87+ def __getitem__ (self , line : FileLine ) -> T :
88+ return self .__contents [line .filename ][line .num ]
89+
90+ def __setitem__ (self , line : FileLine , val : T ) -> None :
91+ if line .filename not in self .__contents :
92+ self .__contents [line .filename ] = {}
93+ if line .num not in self .__contents [line .filename ]:
94+ self .__length += 1
95+ self .__contents [line .filename ][line .num ] = val
96+
97+ def __delitem__ (self , line : FileLine ) -> None :
98+ del self .__contents [line .filename ][line .num ]
99+ if not self .__contents [line .filename ]:
100+ del self .__contents [line .filename ]
101+ self .__length -= 1
58102
59103
60104class FileLineSet (BaseSet ):
0 commit comments