Repository: pantsbuild/pants

Issue:
`NameError: name 'Optional' is not defined` from macros after upgrade to v2.16
**Describe the bug**

We've added type annotations to our `macros.py`, i.e.
```python
def ecr_docker_image(
    *,
    name: Optional[str] = None,
    dependencies: Optional[List[str]] = None,
    image_tags: Optional[List[str]] = None,
    git_tag_prefix: Optional[str] = None,
    latest_tag_prefix: Optional[str] = None,
    buildcache_tag: str = "buildcache",
    image_labels: Optional[Mapping[str, str]] = None,
    tags: Optional[List[str]] = None,
    extra_build_args: Optional[List[str]] = None,
    source: Optional[str] = None,
    target_stage: Optional[str] = None,
    instructions: Optional[List[str]] = None,
    repository: Optional[str] = None,
    context_root: Optional[str] = None,
    push_in_pants_ci: bool = True,
    push_latest: bool = False,
) -> None:
    ...
```

This worked in Pants < 2.16, but when I tried upgrading to 2.16.0a0 `pants` started crashing at startup with:
```
Traceback (most recent call last):
  File "/Users/dan.moran/Library/Caches/nce/496def19ed8a0f8d58b71d0640867bbd94ab2c94f2ca9a49bb59cd109e4a2821/bindings/venvs/2.16.0a0/lib/python3.9/site-packages/pants/engine/internals/build_files.py", line 106, in evaluate_preludes
    exec(content, globals, locals)
  File "build/pants/macros.py", line 6, in <module>
    name: Optional[str] = None,
NameError: name 'Optional' is not defined
```

**Pants version**

2.16.0a0

**OS**

MacOS


Discussion:
Uff.. sorry about that. It's likely from this change https://github.com/pantsbuild/pants/pull/18260/files#diff-5ac95ab1caf4f0c0e4a336a9c28f6c3b748754165aa9a4ef248e9bc9831f4abeR98

Does it work if you add `from __future__ import annotations` to your macros file?

Edit: well, I guess the import-ban will hit it, even if it's not a "real" import. 

Resolving patch:
diff --git a/src/python/pants/engine/internals/build_files.py b/src/python/pants/engine/internals/build_files.py
--- a/src/python/pants/engine/internals/build_files.py
+++ b/src/python/pants/engine/internals/build_files.py
@@ -9,6 +9,7 @@
 import logging
 import os.path
 import sys
+import typing
 from dataclasses import dataclass
 from pathlib import PurePath
 from typing import Any, Sequence, cast
@@ -93,6 +94,7 @@ async def evaluate_preludes(
     )
     globals: dict[str, Any] = {
         **{name: getattr(builtins, name) for name in dir(builtins) if name.endswith("Error")},
+        **{name: getattr(typing, name) for name in dir(typing)},
         # Ensure the globals for each prelude includes the builtin symbols (E.g. `python_sources`)
         # and any build file aliases (e.g. from plugins)
         **parser.symbols,


Test patch:
diff --git a/src/python/pants/engine/internals/build_files_test.py b/src/python/pants/engine/internals/build_files_test.py
--- a/src/python/pants/engine/internals/build_files_test.py
+++ b/src/python/pants/engine/internals/build_files_test.py
@@ -237,6 +237,79 @@ def make_a_target():
     result.symbols["make_a_target"]()
 
 
+def test_prelude_type_hint_code() -> None:
+    # Issue 18435
+    prelude_content = dedent(
+        """\
+        def ecr_docker_image(
+            *,
+            name: Optional[str] = None,
+            dependencies: Optional[List[str]] = None,
+            image_tags: Optional[List[str]] = None,
+            git_tag_prefix: Optional[str] = None,
+            latest_tag_prefix: Optional[str] = None,
+            buildcache_tag: str = "buildcache",
+            image_labels: Optional[Mapping[str, str]] = None,
+            tags: Optional[List[str]] = None,
+            extra_build_args: Optional[List[str]] = None,
+            source: Optional[str] = None,
+            target_stage: Optional[str] = None,
+            instructions: Optional[List[str]] = None,
+            repository: Optional[str] = None,
+            context_root: Optional[str] = None,
+            push_in_pants_ci: bool = True,
+            push_latest: bool = False,
+        ) -> int:
+            return 42
+        """
+    )
+    result = run_prelude_parsing_rule(prelude_content)
+    ecr_docker_image = result.info["ecr_docker_image"]
+    assert ecr_docker_image.signature in (
+        (
+            "(*,"
+            " name: Optional[str] = None,"
+            " dependencies: Optional[List[str]] = None,"
+            " image_tags: Optional[List[str]] = None,"
+            " git_tag_prefix: Optional[str] = None,"
+            " latest_tag_prefix: Optional[str] = None,"
+            " buildcache_tag: str = 'buildcache',"
+            " image_labels: Optional[Mapping[str, str]] = None,"
+            " tags: Optional[List[str]] = None,"
+            " extra_build_args: Optional[List[str]] = None,"
+            " source: Optional[str] = None,"
+            " target_stage: Optional[str] = None,"
+            " instructions: Optional[List[str]] = None,"
+            " repository: Optional[str] = None,"
+            " context_root: Optional[str] = None,"
+            " push_in_pants_ci: bool = True,"
+            " push_latest: bool = False"
+            ") -> int"
+        ),
+        (
+            "(*,"
+            " name: Union[str, NoneType] = None,"
+            " dependencies: Union[List[str], NoneType] = None,"
+            " image_tags: Union[List[str], NoneType] = None,"
+            " git_tag_prefix: Union[str, NoneType] = None,"
+            " latest_tag_prefix: Union[str, NoneType] = None,"
+            " buildcache_tag: str = 'buildcache',"
+            " image_labels: Union[Mapping[str, str], NoneType] = None,"
+            " tags: Union[List[str], NoneType] = None,"
+            " extra_build_args: Union[List[str], NoneType] = None,"
+            " source: Union[str, NoneType] = None,"
+            " target_stage: Union[str, NoneType] = None,"
+            " instructions: Union[List[str], NoneType] = None,"
+            " repository: Union[str, NoneType] = None,"
+            " context_root: Union[str, NoneType] = None,"
+            " push_in_pants_ci: bool = True,"
+            " push_latest: bool = False"
+            ") -> int"
+        ),
+    )
+    assert 42 == ecr_docker_image.value()
+
+
 def test_prelude_docstrings() -> None:
     macro_docstring = "This is the doc-string for `macro_func`."
     prelude_content = dedent(