@@ -8469,352 +8469,23 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self):
84698469 )
84708470
84718471class TestEvaluateForwardRefs (BaseTestCase ):
8472- def test_evaluate_forward_refs (self ):
8473- NoneType = type (None )
8474- T = TypeVar ("T" )
8475- T_default = TypeVar ("T_default" , default = None )
8476- Ts = TypeVarTuple ("Ts" )
8477-
8478- # This variable must have a different name TypeVar
8479- T_local = TypeVar ("T_nonlocal" )
8480-
8481- class X :
8482- T_in_Y = object () # Using X as owner should not access this value
8483-
8484- T_in_Y = TypeVar ("T_in_Y" )
8485- class Y (Generic [T_in_Y ]):
8486- a = "X"
8487- bT = "Y[T_nonlocal]"
8488- # Object with __type_params__
8489- Z = TypeAliasType ("Z" , Y [T_in_Y ], type_params = (T_in_Y ,))
8490- del T_in_Y
8491-
8492- # Assure that class names are not in globals
8493- self .assertNotIn (X .__name__ , globals ())
8494- self .assertNotIn (Y .__name__ , globals ())
8495-
8496- # Namespaces for evaluation
8497- minimal_localns = {
8498- "ForwardRef" : typing .ForwardRef ,
8499- "NoneAlias" : None ,
8500- "NoneType" : NoneType ,
8501- "T" : T ,
8502- "T_default" : T_default ,
8503- "Ts" : Ts ,
8504- "StrAlias" : str ,
8505- }
8506- full_localns = {
8507- ** minimal_localns ,
8508- "X" : X ,
8509- "Y" : Y ,
8510- "Z" : Z ,
8511- }
8512- minimal_globalns = {
8513- "typing" : typing ,
8514- "ForwardRef" : typing .ForwardRef ,
8515- "typing_extensions" : typing_extensions ,
8516- ** vars (typing_extensions ),
8517- }
8518-
8519- # Helper functions to construct test cases
8520- def _unroll_subcase (
8521- annot , expected , format , is_argument , is_class , globalns , localns
8522- ):
8523- owner = expected .get ("owner" , None )
8524- type_params = expected .get ("type_params" , None )
8525- skip_if = expected .get ("skip_if" , False )
8526- if skip_if : # Should only be used in list of dicts
8527- ns = locals ()
8528- skip = all (ns [k ] == v for k , v in skip_if .items ())
8529- if skip :
8530- return None
8531- return (
8532- annot ,
8533- expected [format ],
8534- format ,
8535- is_argument ,
8536- is_class ,
8537- globalns ,
8538- localns ,
8539- type_params ,
8540- owner ,
8541- )
8542-
8543- def unroll_cases (cases ):
8544- """Generator to yield all test case combinations"""
8545- for (
8546- annot ,
8547- expected ,
8548- ), format , is_argument , is_class , globalns , localns in itertools .product (
8549- # annot, expected
8550- cases .items (),
8551- # format
8552- (Format .VALUE , Format .FORWARDREF , Format .STRING ),
8553- # is_argument
8554- (False , True ),
8555- # is_class is not availiable for all versions
8556- (False , True ) if _FORWARD_REF_HAS_CLASS else (None ,),
8557- # globals
8558- (globals (), minimal_globalns ),
8559- # locals
8560- (full_localns , minimal_localns ),
8561- ):
8562- # Argument for ForwardRef
8563- if isinstance (annot , type ):
8564- annot = annot .__name__
8565- else :
8566- annot = str (annot )
8567- # Dict with Format.VALUE, Format.FORWARDREF, Format.STRING entries
8568- if isinstance (expected , dict ):
8569- case = _unroll_subcase (
8570- annot ,
8571- expected ,
8572- format ,
8573- is_argument ,
8574- is_class ,
8575- globalns ,
8576- localns ,
8577- )
8578- yield case
8579- # List of dicts with multiple cases depending on other parameters
8580- elif type (expected ) is list : # noqa: E721 # use `is` because of _ConcatenateGenericAlias
8581- yield from filter (
8582- None ,
8583- (
8584- _unroll_subcase (
8585- annot ,
8586- sub_expected ,
8587- format ,
8588- is_argument ,
8589- is_class ,
8590- globalns ,
8591- localns ,
8592- )
8593- for sub_expected in expected
8594- ),
8595- )
8596- # Single value
8597- else :
8598- # Change expected to TypeError if it will fail typing._type_check
8599- if format != Format .STRING and not inspect .isclass (expected ):
8600- invalid_generic_forms = (Generic , Protocol )
8601- if is_class is False :
8602- invalid_generic_forms += (ClassVar ,)
8603- if is_argument :
8604- invalid_generic_forms += (Final ,)
8605- elif not _FORWARD_REF_HAS_CLASS and is_argument :
8606- invalid_generic_forms += (
8607- ClassVar ,
8608- Final ,
8609- )
8610- if get_origin (expected ) in invalid_generic_forms :
8611- expected = TypeError
8612- type_params = None
8613- owner = None
8614- yield (
8615- annot ,
8616- expected ,
8617- format ,
8618- is_argument ,
8619- is_class ,
8620- globalns ,
8621- localns ,
8622- type_params ,
8623- owner ,
8624- )
8625-
8626- cases = {
8627- # values can be types, dicts or list of dicts.
8628- # Combination of all subdicts with "skip_if" should provide complete coverage,
8629- # i.e. they should come in pairs.
8630- # For non-complete coverage set the value to "Skip: reason".
8631- None : {
8632- Format .VALUE : NoneType ,
8633- Format .FORWARDREF : NoneType ,
8634- Format .STRING : "None" ,
8635- },
8636- "NoneAlias" : {
8637- Format .VALUE : NoneType ,
8638- Format .FORWARDREF : NoneType ,
8639- Format .STRING : "NoneAlias" ,
8640- },
8641- str : str ,
8642- "StrAlias" : {
8643- Format .VALUE : str ,
8644- Format .FORWARDREF : str ,
8645- Format .STRING : "StrAlias" ,
8646- },
8647- Union [str , None , "str" ]: {
8648- Format .VALUE : Optional [str ],
8649- Format .FORWARDREF : Optional [str ],
8650- Format .STRING : str (Union [str , None , "str" ]),
8651- },
8652- "Y.a" : [
8653- {
8654- "skip_if" : {"localns" : minimal_localns , "format" : Format .FORWARDREF },
8655- Format .VALUE : X ,
8656- Format .FORWARDREF : X ,
8657- Format .STRING : "Y.a" ,
8658- },
8659- {
8660- "skip_if" : {"localns" : full_localns , "format" : Format .FORWARDREF },
8661- Format .VALUE : X ,
8662- Format .FORWARDREF : typing .ForwardRef ("Y.a" ),
8663- Format .STRING : "Y.a" ,
8664- },
8665- ],
8666- "Y.bT" : [
8667- # Note: Different to the <3.14 ForwardRef behavior STRING yields "Y.bT"
8668- # and not "Y[T_nonlocal]"
8669- {
8670- "type_params" : (T_local , ),
8671- Format .VALUE : Y [T_local ],
8672- Format .FORWARDREF : Y [T_local ],
8673- Format .STRING : "Y.bT" ,
8674- },
8675- {
8676- "type_params" : None ,
8677- "skip_if" : {"localns" : minimal_localns , "format" : Format .FORWARDREF },
8678- Format .VALUE : NameError ,
8679- Format .FORWARDREF : typing .ForwardRef ("Y[T_nonlocal]" ),
8680- Format .STRING : "Y.bT" ,
8681- },
8682- {
8683- "type_params" : None ,
8684- "skip_if" : {"localns" : full_localns , "format" : Format .FORWARDREF },
8685- Format .VALUE : NameError ,
8686- Format .FORWARDREF : typing .ForwardRef ("Y.bT" ),
8687- Format .STRING : "Y.bT" ,
8688- },
8689- ],
8690- # Special cases for typing._type_check.
8691- # Note: Depending on `is_class` and `is_argument` will raise TypeError
8692- # therefore `expected` is converted in the generator.
8693- ClassVar [None ]: ClassVar [None ],
8694- Final [None ]: Final [None ],
8695- Protocol [T ]: Protocol [T ],
8696- # Plain usage
8697- Protocol : {
8698- Format .VALUE : TypeError ,
8699- Format .FORWARDREF : TypeError ,
8700- Format .STRING : "Protocol" ,
8701- },
8702- Generic : {
8703- Format .VALUE : TypeError ,
8704- Format .FORWARDREF : TypeError ,
8705- Format .STRING : "Generic" ,
8706- },
8707- Final : (
8708- [
8709- {
8710- "skip_if" : (
8711- {"is_class" : False }
8712- if _FORWARD_REF_HAS_CLASS
8713- else {"is_argument" : True }
8714- ),
8715- Format .VALUE : Final ,
8716- Format .FORWARDREF : Final ,
8717- Format .STRING : str (Final ),
8718- },
8719- {
8720- "skip_if" : (
8721- {"is_class" : True }
8722- if _FORWARD_REF_HAS_CLASS
8723- else {"is_argument" : False }
8724- ),
8725- Format .VALUE : TypeError ,
8726- Format .FORWARDREF : TypeError ,
8727- Format .STRING : str (Final ),
8728- },
8729- ]
8730- ),
8731- }
8732-
8733- # Test cases
8734- for (
8735- annot ,
8736- expected ,
8737- format ,
8738- is_argument ,
8739- is_class ,
8740- globalns ,
8741- localns ,
8742- type_params ,
8743- owner ,
8744- ) in unroll_cases (cases ):
8745- if _FORWARD_REF_HAS_CLASS :
8746- ref = typing .ForwardRef (annot , is_argument = is_argument , is_class = is_class )
8747- else :
8748- ref = typing .ForwardRef (annot , is_argument = is_argument )
8749- with self .subTest (
8750- annot = annot ,
8751- expected = expected ,
8752- format = format ,
8753- is_argument = is_argument ,
8754- is_class = is_class ,
8755- type_params = type_params ,
8756- minimal_globalns = globalns == minimal_globalns ,
8757- minimal_localns = localns == minimal_localns ,
8758- owner = owner ,
8759- ):
8760- # Skip test
8761- if isinstance (expected , str ) and expected .lower ().startswith ("skip" ):
8762- self .skipTest (expected )
8763- # Expected Exceptions
8764- if inspect .isclass (expected ) and issubclass (expected , Exception ):
8765- with self .assertRaises (expected ):
8766- evaluate_forward_ref (
8767- ref , locals = localns , globals = globalns , format = format ,
8768- )
8769- continue
8770- # Adjust expected:
8771- # STRING
8772- if format == Format .STRING :
8773- expected = (
8774- str (expected )
8775- if not isinstance (expected , type )
8776- else expected .__name__
8777- )
8778- # FORWARDREF with X, Y not in locals stays a forward ref
8779- elif (
8780- format == Format .FORWARDREF
8781- and (get_origin (expected ) in (X , Y ) or expected in (X , Y ))
8782- and localns is minimal_localns
8783- ):
8784- expected = ref
8785- # VALUE with X, Y not in locals will raise NameError
8786- elif (
8787- format == Format .VALUE
8788- and (get_origin (expected ) in (X , Y ) or expected in (X , Y ))
8789- and localns is minimal_localns
8790- ):
8791- with self .assertRaisesRegex (NameError , "X|Y" ):
8792- evaluate_forward_ref (
8793- ref ,
8794- locals = localns ,
8795- globals = globalns ,
8796- format = format ,
8797- type_params = type_params ,
8798- owner = owner ,
8799- )
8800- continue
8801-
8802- result = evaluate_forward_ref (
8803- ref ,
8804- locals = localns ,
8805- globals = globalns ,
8806- format = format ,
8807- type_params = type_params ,
8808- owner = owner ,
8809- )
8810- self .assertEqual (result , expected )
8811-
88128472 def test_forward_ref_fallback (self ):
88138473 with self .assertRaises (NameError ):
8814- evaluate_forward_ref (typing .ForwardRef ("unbound" ), format = Format . VALUE )
8815- ref = typing .ForwardRef ("unbound " )
8474+ evaluate_forward_ref (typing .ForwardRef ("doesntexist" ) )
8475+ ref = typing .ForwardRef ("doesntexist " )
88168476 self .assertIs (evaluate_forward_ref (ref , format = Format .FORWARDREF ), ref )
88178477
8478+ class X :
8479+ unresolvable = "doesnotexist2"
8480+
8481+ evaluated_ref = evaluate_forward_ref (
8482+ typing .ForwardRef ("X.unresolvable" ),
8483+ locals = {"X" : X },
8484+ type_params = None ,
8485+ format = Format .FORWARDREF ,
8486+ )
8487+ self .assertEqual (evaluated_ref , typing .ForwardRef ("doesnotexist2" ))
8488+
88188489 def test_evaluate_with_type_params (self ):
88198490 # Use a T name that is not in globals
88208491 self .assertNotIn ("Tx" , globals ())
@@ -8930,9 +8601,6 @@ def test_name_lookup_without_eval(self):
89308601 with support .swap_attr (builtins , "int" , dict ):
89318602 self .assertIs (evaluate_forward_ref (typing .ForwardRef ("int" )), dict )
89328603
8933- with self .assertRaises (NameError ):
8934- evaluate_forward_ref (typing .ForwardRef ("doesntexist" ))
8935-
89368604 def test_nested_strings (self ):
89378605 # This variable must have a different name TypeVar
89388606 Tx = TypeVar ("Tx" )
@@ -8968,6 +8636,21 @@ class Y(Generic[Tx]):
89688636 self .skipTest ("Nested string 'StrAlias' is not resolved in 3.8 and 3.10" )
89698637 self .assertEqual (get_args (evaluated_ref3 ), (Z [str ],))
89708638
8639+ def test_invalid_special_forms (self ):
8640+ # tests _lax_type_check to (not) raise error similar to the typing module
8641+ with self .assertRaisesRegex (TypeError , "Plain" ):
8642+ evaluate_forward_ref (typing .ForwardRef ("Protocol" ), globals = vars (typing ))
8643+ with self .assertRaisesRegex (TypeError , "Plain" ):
8644+ evaluate_forward_ref (typing .ForwardRef ("Generic" ), globals = vars (typing ))
8645+ if _FORWARD_REF_HAS_CLASS :
8646+ self .assertIs (evaluate_forward_ref (typing .ForwardRef ("Final" , is_class = True ), globals = vars (typing )), Final )
8647+ with self .assertRaisesRegex (TypeError , "Plain" ):
8648+ evaluate_forward_ref (typing .ForwardRef ("Final" ), globals = vars (typing ))
8649+ else :
8650+ self .assertIs (evaluate_forward_ref (typing .ForwardRef ("Final" , is_argument = False ), globals = vars (typing )), Final )
8651+ with self .assertRaisesRegex (TypeError , "Plain" ):
8652+ evaluate_forward_ref (typing .ForwardRef ("Final" ), globals = vars (typing ))
8653+
89718654
89728655if __name__ == '__main__' :
89738656 main ()
0 commit comments