ruff/crates/ty_python_semantic/resources/mdtest/annotations/string.md
Charlie Marsh 682d29c256
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Blocked by required conditions
CI / cargo test (macos-latest) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / ty completion evaluation (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
CI / benchmarks walltime (small|large) (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
[ty] Avoid enforcing standalone expression for tests in f-strings (#21967)
## Summary

Based on what we do elsewhere and my understanding of "standalone"
here...

Closes https://github.com/astral-sh/ty/issues/1865.
2025-12-15 22:31:04 -05:00

5 KiB

String annotations

Simple

def f(v: "int"):
    reveal_type(v)  # revealed: int

Nested

def f(v: "'int'"):
    reveal_type(v)  # revealed: int

Type expression

def f1(v: "int | str", w: "tuple[int, str]"):
    reveal_type(v)  # revealed: int | str
    reveal_type(w)  # revealed: tuple[int, str]

Partial

def f(v: tuple[int, "str"]):
    reveal_type(v)  # revealed: tuple[int, str]

Deferred

def f(v: "Foo"):
    reveal_type(v)  # revealed: Foo

class Foo: ...

Deferred (undefined)

# error: [unresolved-reference]
def f(v: "Foo"):
    reveal_type(v)  # revealed: Unknown

Partial deferred

def f(v: int | "Foo"):
    reveal_type(v)  # revealed: int | Foo

class Foo: ...

typing.Literal

from typing import Literal

def f1(v: Literal["Foo", "Bar"], w: 'Literal["Foo", "Bar"]'):
    reveal_type(v)  # revealed: Literal["Foo", "Bar"]
    reveal_type(w)  # revealed: Literal["Foo", "Bar"]

class Foo: ...

Various string kinds

def f1(
    # error: [raw-string-type-annotation] "Type expressions cannot use raw string literal"
    a: r"int",
    # error: [fstring-type-annotation] "Type expressions cannot use f-strings"
    b: f"int",
    # error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
    c: b"int",
    d: "int",
    # error: [implicit-concatenated-string-type-annotation] "Type expressions cannot span multiple string literals"
    e: "in" "t",
    # error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
    f: "\N{LATIN SMALL LETTER I}nt",
    # error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
    g: "\x69nt",
    h: """int""",
    # error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
    i: "b'int'",
):
    reveal_type(a)  # revealed: Unknown
    reveal_type(b)  # revealed: Unknown
    reveal_type(c)  # revealed: Unknown
    reveal_type(d)  # revealed: int
    reveal_type(e)  # revealed: Unknown
    reveal_type(f)  # revealed: Unknown
    reveal_type(g)  # revealed: Unknown
    reveal_type(h)  # revealed: int
    reveal_type(i)  # revealed: Unknown

Various string kinds in typing.Literal

from typing import Literal

def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]):
    reveal_type(v)  # revealed: Literal["a", "b", "de", "f", "g", "h", b"c"]

Class variables

MyType = int

class Aliases:
    MyType = str

    forward: "MyType" = "value"
    not_forward: MyType = "value"

reveal_type(Aliases.forward)  # revealed: str
reveal_type(Aliases.not_forward)  # revealed: str

Annotated assignment

a: "int" = 1
b: "'int'" = 1
c: "Foo"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Foo`"
d: "Foo" = 1

class Foo: ...

c = Foo()

reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Literal[1]
reveal_type(c)  # revealed: Foo
reveal_type(d)  # revealed: Foo

Parameter

TODO: Add tests once parameter inference is supported

Invalid expressions

The expressions in these string annotations aren't valid expressions in this context but we shouldn't panic.

# Regression test for https://github.com/astral-sh/ty/issues/1865
# error: [fstring-type-annotation]
stringified_fstring_with_conditional: "f'{1 if 1 else 1}'"
# error: [fstring-type-annotation]
stringified_fstring_with_boolean_expression: "f'{1 or 2}'"
# error: [fstring-type-annotation]
stringified_fstring_with_generator_expression: "f'{(i for i in range(5))}'"
# error: [fstring-type-annotation]
stringified_fstring_with_list_comprehension: "f'{[i for i in range(5)]}'"
# error: [fstring-type-annotation]
stringified_fstring_with_dict_comprehension: "f'{ {i: i for i in range(5)} }'"
# error: [fstring-type-annotation]
stringified_fstring_with_set_comprehension: "f'{ {i for i in range(5)} }'"

a: "1 or 2"
b: "(x := 1)"
# error: [invalid-type-form]
c: "1 + 2"
# Regression test for https://github.com/astral-sh/ty/issues/1847
# error: [invalid-type-form]
c2: "a*(i for i in [])"
d: "lambda x: x"
e: "x if True else y"
f: "{'a': 1, 'b': 2}"
g: "{1, 2}"
h: "[i for i in range(5)]"
i: "{i for i in range(5)}"
j: "{i: i for i in range(5)}"
k: "(i for i in range(5))"
l: "await 1"
# error: [invalid-syntax-in-forward-annotation]
m: "yield 1"
# error: [invalid-syntax-in-forward-annotation]
n: "yield from 1"
o: "1 < 2"
p: "call()"
r: "[1, 2]"
s: "(1, 2)"

Multi line annotation

Quoted type annotations should be parsed as if surrounded by parentheses.

def valid(
    a1: """(
      int |
      str
  )
  """,
    a2: """
     int |
       str
  """,
):
    reveal_type(a1)  # revealed: int | str
    reveal_type(a2)  # revealed: int | str

def invalid(
    # error: [invalid-syntax-in-forward-annotation]
    a1: """
  int |
str)
""",
    # error: [invalid-syntax-in-forward-annotation]
    a2: """
  int) |
str
""",
    # error: [invalid-syntax-in-forward-annotation]
    a3: """
      (int)) """,
):
    pass