@@ -100,7 +100,57 @@ def _unasync_file(self, filepath):
100100 def _unasync_tokens (self , tokens ):
101101 # TODO __await__, ...?
102102 used_space = None
103+ context = None # Can be `None`, `"func_decl"`, `"func_name"`, `"arg_list"`, `"arg_list_end"`, `"return_type"`
104+ brace_depth = 0
105+ typing_ctx = False
106+
103107 for space , toknum , tokval in tokens :
108+ # Update context state tracker
109+ if context is None and toknum == std_tokenize .NAME and tokval == "def" :
110+ context = "func_decl"
111+ elif context == "func_decl" and toknum == std_tokenize .NAME :
112+ context = "func_name"
113+ elif context == "func_name" and toknum == std_tokenize .OP and tokval == "(" :
114+ context = "arg_list"
115+ elif context == "arg_list" :
116+ if toknum == std_tokenize .OP and tokval in ("(" , "[" ):
117+ brace_depth += 1
118+ elif (
119+ toknum == std_tokenize .OP
120+ and tokval in (")" , "]" )
121+ and brace_depth >= 1
122+ ):
123+ brace_depth -= 1
124+ elif toknum == std_tokenize .OP and tokval == ")" :
125+ context = "arg_list_end"
126+ elif toknum == std_tokenize .OP and tokval == ":" and brace_depth < 1 :
127+ typing_ctx = True
128+ elif toknum == std_tokenize .OP and tokval == "," and brace_depth < 1 :
129+ typing_ctx = False
130+ elif (
131+ context == "arg_list_end"
132+ and toknum == std_tokenize .OP
133+ and tokval == "->"
134+ ):
135+ context = "return_type"
136+ typing_ctx = True
137+ elif context == "return_type" :
138+ if toknum == std_tokenize .OP and tokval in ("(" , "[" ):
139+ brace_depth += 1
140+ elif (
141+ toknum == std_tokenize .OP
142+ and tokval in (")" , "]" )
143+ and brace_depth >= 1
144+ ):
145+ brace_depth -= 1
146+ elif toknum == std_tokenize .OP and tokval == ":" :
147+ context = None
148+ typing_ctx = False
149+ else : # Something unexpected happend - reset state
150+ context = None
151+ brace_depth = 0
152+ typing_ctx = False
153+
104154 if tokval in ["async" , "await" ]:
105155 # When removing async or await, we want to use the whitespace that
106156 # was before async/await before the next token so that
@@ -111,8 +161,34 @@ def _unasync_tokens(self, tokens):
111161 if toknum == std_tokenize .NAME :
112162 tokval = self ._unasync_name (tokval )
113163 elif toknum == std_tokenize .STRING :
114- left_quote , name , right_quote = tokval [0 ], tokval [1 :- 1 ], tokval [- 1 ]
115- tokval = left_quote + self ._unasync_name (name ) + right_quote
164+ # Strings in typing context are forward-references and should be unasyncified
165+ quote = ""
166+ prefix = ""
167+ while ord (tokval [0 ]) in range (ord ("a" ), ord ("z" ) + 1 ):
168+ prefix += tokval [0 ]
169+ tokval = tokval [1 :]
170+
171+ if tokval .startswith ('"""' ) and tokval .endswith ('"""' ):
172+ quote = '"""' # Broken syntax highlighters workaround: """
173+ elif tokval .startswith ("'''" ) and tokval .endswith ("'''" ):
174+ quote = "'''" # Broken syntax highlighters wokraround: '''
175+ elif tokval .startswith ('"' ) and tokval .endswith ('"' ):
176+ quote = '"'
177+ elif tokval .startswith ("'" ) and tokval .endswith (
178+ "'"
179+ ): # pragma: no branch
180+ quote = "'"
181+ assert (
182+ len (quote ) > 0
183+ ), "Quoting style of string {0!r} unknown" .format (tokval )
184+ stringval = tokval [len (quote ) : - len (quote )]
185+ if typing_ctx :
186+ stringval = _untokenize (
187+ self ._unasync_tokens (_tokenize (StringIO (stringval )))
188+ )
189+ else :
190+ stringval = self ._unasync_name (stringval )
191+ tokval = prefix + quote + stringval + quote
116192 elif toknum == std_tokenize .COMMENT and tokval .startswith (
117193 _TYPE_COMMENT_PREFIX
118194 ):
@@ -193,7 +269,7 @@ def _tokenize(f):
193269 # Somehow Python 3.5 and below produce the ENDMARKER in a way that
194270 # causes superfluous continuation lines to be generated
195271 if tok .type != std_tokenize .ENDMARKER :
196- yield ("" , std_tokenize .STRING , " \\ \n " )
272+ yield (" " , std_tokenize .NEWLINE , "\\ \n " )
197273 last_end = (tok .start [0 ], 0 )
198274
199275 space = ""
0 commit comments