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