Repository: cython/cython

Issue:
[BUG] NameError: name 'Dict' is not defined
### Describe the bug

When import `typing` and accessing `typing.Dict` a `NameError` is raised.

This does not occur with `Cython<3`.

### Code to reproduce the behaviour:

```cython
import typing
typing.Dict
```

### Expected behaviour

An exception is not raised.

### OS

macOS, Linux

### Python version

3.10.8

### Cython version

3.0.0

### Additional context

_No response_


Discussion:
Hmm. I'm fairly sure we test more complicated versions of that so I wonder what's going wrong with the simple version. Thanks for the report
The bug occurs for us in a "more complex" scenario, but I was able to reproduce with this simple example.

If it helps to have our actual source of the issue for context: https://github.com/DataDog/dd-trace-py/blob/7f73c55c66fc8201dda021fe2fec2d812feb3d06/ddtrace/profiling/_threading.pyx#L103
Definitely seems like it is there, so curious why attribute access doesn't work 🤷🏻‍♂️ 

--- 

```cython
import typing
print(dir(typing))
```

```
[..., 'Dict', ...]
```
---

```cython
import typing
print(getattr(typing, "Dict"))
```

```
typing.Dict
```

Resolving patch:
diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py
--- a/Cython/Compiler/Builtin.py
+++ b/Cython/Compiler/Builtin.py
@@ -572,6 +572,7 @@ def get_known_standard_library_module_scope(module_name):
             var_entry.is_variable = True
             var_entry.scope = mod
             entry.as_variable = var_entry
+            entry.known_standard_library_import = "%s.%s" % (module_name, name)
 
         for name in ['ClassVar', 'Optional']:
             name = EncodedString(name)
@@ -582,6 +583,7 @@ def get_known_standard_library_module_scope(module_name):
             var_entry.is_variable = True
             var_entry.scope = mod
             entry.as_variable = var_entry
+            entry.known_standard_library_import = "%s.%s" % (module_name, name)
         _known_module_scopes[module_name] = mod
     elif module_name == "dataclasses":
         mod = ModuleScope(module_name, None, None)
@@ -592,6 +594,7 @@ def get_known_standard_library_module_scope(module_name):
         var_entry.is_pyglobal = True
         var_entry.scope = mod
         entry.as_variable = var_entry
+        entry.known_standard_library_import = "%s.InitVar" % module_name
         for name in ["dataclass", "field"]:
             mod.declare_var(EncodedString(name), PyrexTypes.py_object_type, pos=None)
         _known_module_scopes[module_name] = mod
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -7341,7 +7341,7 @@ def analyse_as_cimported_attribute_node(self, env, target):
         module_scope = self.obj.analyse_as_module(env)
         if module_scope:
             entry = module_scope.lookup_here(self.attribute)
-            if entry and (
+            if entry and not entry.known_standard_library_import and (
                     entry.is_cglobal or entry.is_cfunction
                     or entry.is_type or entry.is_const):
                 return self.as_name_node(env, entry, target)


Test patch:
diff --git a/tests/run/pep526_variable_annotations.py b/tests/run/pep526_variable_annotations.py
--- a/tests/run/pep526_variable_annotations.py
+++ b/tests/run/pep526_variable_annotations.py
@@ -8,6 +8,8 @@
 
 import cython
 
+# Don't add FrozenSet to this list - it's necessary for one of the tests
+# that it isn't a module global name
 from typing import Dict, List, TypeVar, Optional, Generic, Tuple
 
 try:
@@ -267,18 +269,24 @@ def test_use_typing_attributes_as_non_annotations():
     x1 = typing.Tuple
     x2 = typing.Tuple[int]
     y1 = typing.Optional
-    y2 = typing.Optional[typing.Dict]
+    # It's important for the test that FrozenSet isn't available in the module namespace,
+    # since one bug would have looked it up there rather than as an attribute of typing
+    y2 = typing.Optional[typing.FrozenSet]
     z1 = Optional
     z2 = Optional[Dict]
     # The result of printing "Optional[type]" is slightly version-dependent
     # so accept both possible forms
-    allowed_optional_strings = [
+    allowed_optional_frozenset_strings = [
+        "typing.Union[typing.FrozenSet, NoneType]",
+        "typing.Optional[typing.FrozenSet]"
+    ]
+    allowed_optional_dict_strings = [
         "typing.Union[typing.Dict, NoneType]",
         "typing.Optional[typing.Dict]"
     ]
     print(x1, x2)
-    print(y1, str(y2) in allowed_optional_strings)
-    print(z1, str(z2) in allowed_optional_strings)
+    print(y1, str(y2) in allowed_optional_frozenset_strings  or  str(y2))
+    print(z1, str(z2) in allowed_optional_dict_strings  or  str(z2))
 
 def test_optional_ctuple(x: typing.Optional[tuple[float]]):
     """