Graham, Paul, and Richard brought up a frequent use case for SQL queries: adding literal strings based on a flag or joining an array of literals. For example:


```

SQL = "SELECT * FROM table WHERE col = %s"

if limit:

    SQL += " LIMIT 1"

```


The idea is that we want strings constructed from statically-known literal strings, as Jia pointed out. These are either


  • a literal string expression ("foo")

  • conditional branches (`if b: return "foo"; else: return "bar"`)

  • narrowing using a guard (`if s == “foo”:`)

  • `+` on literals

  • `join` on literals


We want to exclude strings that are constructed from some expression of type `str` since they are not statically-known to have been constructed from literals.


Bikeshedding:

  • I'm ok with `AnyLiteral[str]` since `Literal[str]` can be somewhat confusing. The extra import is fine since this is mostly going to be used in libraries, not in user-written code, and we want the `[str]` parameter since we also want this for `[int]`, etc.
  • Another option is `MadeFromLiterals[str]`, which is the most accurate description, but is also a bit long.
  • Finally, we could have separate classes like `AnyStrLiteral`, `AnyIntLiteral`, etc. This would allow us to subclass `str` and override any methods explicitly, but may be harder to explain. Typecheckers would need to look up any method calls on `Literal["hello"]` on `AnyStrLiteral`, not `str`.


Compatibility: This goes as `AnyLiteral["foo"] <: AnyLiteral[str] <: str` but not in the other direction. (`A <: B` means A is compatible with B.)


Potential objection: Can an attacker contrive a malicious literal string by giving an input that traverses different branches of benign code and appends known literal strings in some order? Yes, but this seems a remote possibility. Based on discussions with security folks, I think that’s a tradeoff we can accept given that disallowing `+` would rule out natural idioms like appending a “LIMIT 1” to a query.

> But I don't think we have a way to define a method overload only on `Literal[str]` and not on `str`? Maybe could use self-types or something for this, but it's something that would require additional support I think.


Carl: Sure, we can add an overload specifically for literals with an annotation for `self`. That will require literal strings for both the input list and the delimiter. This looks like:


```

class str(Sequence[str]):

    ...


    @overload

    def join(self: Literal[str], iterable: Iterable[Literal[str]]) -> Literal[str]: ...

    @overload

    def join(self, iterable: Iterable[str]) -> str: ...


    @overload

    def __add__(self: Literal[str], other: Literal[str]) -> Literal[str]: ...

    @overload

    def __add__(self, other: str) -> str: ...



from typing import Literal


def connection_query(sql: Literal[str], value: str) -> None: ...


def my_query(value: str, limit: bool) -> None:

    SQL = "SELECT * FROM table WHERE col = %s"

    if limit:

SQL += " LIMIT 1"


    connection_query(SQL, value) # OK

    connection_query(SQL + value, value) # Error: Expected Literal[str], got str.


def foo(s: str) -> None:

    y = ", ".join(["a", "b", "c"])

    reveal_type(y) # => Literal[str]


    y2 = ", ".join(["a", "b", s])

    reveal_type(y2) # => str


    xs: list[Literal[str]]

    y3 = ", ".join(xs)

    reveal_type(y3) # => Literal[str]


    y4 = s.join(xs)

    reveal_type(y4) # => str because the delimiter is `str`.

```


The above example works with Pyre after changing the stub for `str`. Another option is to special-case `+` and `join` within typecheckers like you said in case we don’t want to change the `str` stubs. (Or, if we go down the `AnyStrLiteral` route, these specialized stubs would live in that class.)

--
S Pradeep Kumar