@@ -261,19 +261,6 @@ def strip_raw_and_b(string):
261261 return raw , has_b , string
262262
263263
264- def ensure_module_or_create_fake (mod_name ):
265- """Create a fake module if it does not already exist."""
266- return handle_indentation ("""
267- try:
268- {mod_name}
269- except:
270- {mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
271- else:
272- if not _coconut.isinstance({mod_name}, _coconut.types.ModuleType):
273- {mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
274- """ ).format (mod_name = mod_name )
275-
276-
277264def get_imported_names (imports ):
278265 """Returns all the names imported by imports = [[imp1], [imp2, as], ...] and whether there is a star import."""
279266 saw_names = []
@@ -422,6 +409,60 @@ def call_decorators(decorators, func_name):
422409 return out
423410
424411
412+ def dedent_d_string (text , loc , placeholder = None ):
413+ """Apply PEP 822 dedentation to string contents.
414+ The text must start with a newline (the required newline after opening quotes).
415+ If placeholder is given, it is treated as non-whitespace for indentation calculation
416+ but preserved in the output."""
417+
418+ if not text .startswith ("\n " ):
419+ raise CoconutDeferredSyntaxError ("d-string contents must start with a newline after opening quotes" , loc )
420+ text = text [1 :] # remove leading newline (not included in result)
421+
422+ lines = text .split ("\n " )
423+
424+ # determine common indentation
425+ # blank lines are ignored except the last line (closing quotes line)
426+ indent = None
427+ for i , line in enumerate (lines ):
428+ is_last = i == len (lines ) - 1
429+ # X is an arbitrary non-whitespace character
430+ check_line = line .replace (placeholder , "X" ) if placeholder else line
431+ if not is_last and check_line .strip () == "" :
432+ continue
433+ stripped = check_line .lstrip ()
434+ line_indent = check_line [:len (check_line ) - len (stripped )]
435+ if indent is None :
436+ indent = line_indent
437+ else :
438+ common = ""
439+ for a , b in zip (indent , line_indent ):
440+ if a == b :
441+ common += a
442+ else :
443+ break
444+ indent = common
445+
446+ if indent is None :
447+ indent = ""
448+
449+ # apply dedentation
450+ result_lines = []
451+ for i , line in enumerate (lines ):
452+ is_last = i == len (lines ) - 1
453+ check_line = line .replace (placeholder , "X" ) if placeholder else line
454+ if check_line .strip () == "" and not is_last :
455+ result_lines .append ("" )
456+ elif line .startswith (indent ):
457+ result_lines .append (line [len (indent ):])
458+ elif indent .startswith (check_line ) and check_line .strip () == "" :
459+ result_lines .append ("" )
460+ else :
461+ raise CoconutDeferredSyntaxError ("inconsistent indentation in d-string" , loc )
462+
463+ return "\n " .join (result_lines )
464+
465+
425466def get_cache_path (codepath ):
426467 """Get the cache filename to use for the given codepath."""
427468 code_dir , code_fname = os .path .split (codepath )
@@ -4002,6 +4043,21 @@ def anon_namedtuple_handle(self, original, loc, tokens):
40024043
40034044 return self .make_namedtuple_call (None , names , types , of_args = items )
40044045
4046+ def ensure_module_or_create_fake (self , mod_name ):
4047+ """Create a fake module if it does not already exist."""
4048+ return handle_indentation ("""
4049+ try:
4050+ {mod_name} {type_ignore}
4051+ except:
4052+ {mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
4053+ else:
4054+ if not _coconut.isinstance({mod_name}, _coconut.types.ModuleType): {type_ignore}
4055+ {mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
4056+ """ ).format (
4057+ mod_name = mod_name ,
4058+ type_ignore = self .type_ignore_comment (),
4059+ )
4060+
40054061 def _make_import_stmt (self , imp_from , imp , imp_as , raw = False , lazy = False ):
40064062 """Generate an import statement."""
40074063 if not raw and imp != "*" :
@@ -4028,7 +4084,7 @@ def _make_import_stmt(self, imp_from, imp, imp_as, raw=False, lazy=False):
40284084 fake_mods = (imp_from if imp_from is not None else imp ).split ("." )
40294085 for i in range (1 , len (fake_mods )):
40304086 mod_name = "." .join (fake_mods [:i ])
4031- out_lines .append (ensure_module_or_create_fake (mod_name ))
4087+ out_lines .append (self . ensure_module_or_create_fake (mod_name ))
40324088 bind_to = imp_as if imp_as is not None else imp
40334089 out_lines .append ('{bind_to} = _coconut_lazy_module("{module}"){attr} {type_ignore}' .format (
40344090 bind_to = bind_to ,
@@ -4069,7 +4125,7 @@ def single_import(self, loc, path, imp_as, type_ignore=False, lazy=False):
40694125 fake_mods = imp_as .split ("." )
40704126 for i in range (1 , len (fake_mods )):
40714127 mod_name = "." .join (fake_mods [:i ])
4072- out .append (ensure_module_or_create_fake (mod_name ))
4128+ out .append (self . ensure_module_or_create_fake (mod_name ))
40734129 out .append ("." .join (fake_mods ) + " = " + import_as_var )
40744130 else :
40754131 out .append (self ._make_import_stmt (imp_from , imp , imp_as , lazy = lazy ))
@@ -4131,22 +4187,14 @@ def universal_import(self, loc, imports, imp_from=None, lazy=False):
41314187 stmts .extend (more_stmts )
41324188 else :
41334189 old_imp , new_imp , version_check = paths
4134- # we have to do this craziness to get mypy to statically handle the version check
4190+ # TODO: we have to do this craziness to get mypy to statically handle the version check
41354191 stmts .append (
41364192 handle_indentation ("""
4137- try:
4138- {store_var} = sys {type_ignore}
4139- except _coconut.NameError:
4140- {store_var} = _coconut_sentinel
4141- sys = _coconut_sys
4142- if sys.version_info >= {version_check}:
4143- {new_imp}
4193+ if _coconut.typing.TYPE_CHECKING or _coconut_sys.version_info >= {version_check}:
4194+ {new_imp} {type_ignore}
41444195else:
41454196 {old_imp}
4146- if {store_var} is not _coconut_sentinel:
4147- sys = {store_var}
41484197 """ ).format (
4149- store_var = self .get_temp_var ("sys" , loc ),
41504198 version_check = version_check ,
41514199 new_imp = "\n " .join (self .single_import (loc , new_imp , imp_as , lazy = lazy )),
41524200 # should only type: ignore the old import
@@ -4158,18 +4206,17 @@ def universal_import(self, loc, imports, imp_from=None, lazy=False):
41584206
41594207 def import_handle (self , original , loc , tokens ):
41604208 """Universalizes imports."""
4161- # First token is always either "lazy" or "" (from Optional default)
4162- internal_assert (tokens [0 ] in ("lazy" , "" ), original , loc , "invalid import type token" , tokens [0 ])
4163- lazy = tokens [0 ] == "lazy"
4164- tokens = tokens [1 :]
4165-
4166- if len (tokens ) == 1 :
4167- imp_from , imports = None , tokens [0 ]
4168- elif len (tokens ) == 2 :
4169- imp_from , imports = tokens
4209+ if len (tokens ) == 2 :
4210+ imp_from = None
4211+ lazy , imports = tokens
4212+ elif len (tokens ) == 3 :
4213+ lazy , imp_from , imports = tokens
41704214 else :
41714215 raise CoconutInternalException ("invalid import tokens" , tokens )
41724216
4217+ internal_assert (lazy in ("lazy" , "" ), original , loc , "invalid import type token" , lazy )
4218+ lazy = lazy == "lazy"
4219+
41734220 if imp_from == "__future__" :
41744221 if lazy :
41754222 raise self .make_err (CoconutSyntaxError , "cannot lazy import from __future__" , original , loc )
@@ -4893,61 +4940,7 @@ def t_string_handle(self, original, loc, tokens):
48934940 """Process Python 3.14 template strings."""
48944941 return self .f_string_handle (original , loc , tokens , is_t = True )
48954942
4896- @staticmethod
4897- def dedent_d_string (text , loc , placeholder = None ):
4898- """Apply PEP 822 dedentation to string contents.
4899- The text must start with a newline (the required newline after opening quotes).
4900- If placeholder is given, it is treated as non-whitespace for indentation calculation
4901- but preserved in the output."""
4902-
4903- if not text .startswith ("\n " ):
4904- raise CoconutDeferredSyntaxError ("d-string contents must start with a newline after opening quotes" , loc )
4905- text = text [1 :] # remove leading newline (not included in result)
4906-
4907- lines = text .split ("\n " )
4908-
4909- # determine common indentation
4910- # blank lines are ignored except the last line (closing quotes line)
4911- indent = None
4912- for i , line in enumerate (lines ):
4913- is_last = i == len (lines ) - 1
4914- # X is an arbitrary non-whitespace character
4915- check_line = line .replace (placeholder , "X" ) if placeholder else line
4916- if not is_last and check_line .strip () == "" :
4917- continue
4918- stripped = check_line .lstrip ()
4919- line_indent = check_line [:len (check_line ) - len (stripped )]
4920- if indent is None :
4921- indent = line_indent
4922- else :
4923- common = ""
4924- for a , b in zip (indent , line_indent ):
4925- if a == b :
4926- common += a
4927- else :
4928- break
4929- indent = common
4930-
4931- if indent is None :
4932- indent = ""
4933-
4934- # apply dedentation
4935- result_lines = []
4936- for i , line in enumerate (lines ):
4937- is_last = i == len (lines ) - 1
4938- check_line = line .replace (placeholder , "X" ) if placeholder else line
4939- if check_line .strip () == "" and not is_last :
4940- result_lines .append ("" )
4941- elif line .startswith (indent ):
4942- result_lines .append (line [len (indent ):])
4943- elif indent .startswith (check_line ) and check_line .strip () == "" :
4944- result_lines .append ("" )
4945- else :
4946- raise CoconutDeferredSyntaxError ("inconsistent indentation in d-string" , loc )
4947-
4948- return "\n " .join (result_lines )
4949-
4950- def d_string_handle (self , original , loc , tokens ):
4943+ def d_string_handle (self , loc , tokens ):
49514944 """Process PEP 822 d-strings (dedented strings)."""
49524945 string , = tokens
49534946
@@ -4962,7 +4955,7 @@ def d_string_handle(self, original, loc, tokens):
49624955 raise CoconutDeferredSyntaxError ("d-string prefix requires triple-quoted string" , loc )
49634956
49644957 # apply dedentation
4965- text = self . dedent_d_string (text , loc )
4958+ text = dedent_d_string (text , loc )
49664959
49674960 # Python 2 only supports br"..." not rb"..."
49684961 return ("b" if has_b else "" ) + ("r" if raw else "" ) + self .wrap_str (text , strchar )
@@ -4989,7 +4982,7 @@ def d_f_string_handle(self, original, loc, tokens, is_t=False):
49894982 placeholder = "\x00 "
49904983 full_text = placeholder .join (string_parts )
49914984 internal_assert (lambda : full_text .count (placeholder ) == len (string_parts ) - 1 , "placeholder character found in d-string contents" , string_parts )
4992- new_parts = self . dedent_d_string (full_text , loc , placeholder = placeholder ).split (placeholder )
4985+ new_parts = dedent_d_string (full_text , loc , placeholder = placeholder ).split (placeholder )
49934986
49944987 # re-wrap as f-string ref and delegate to f_string_handle
49954988 new_ref = self .wrap_f_str (strchar , new_parts , exprs )
0 commit comments