basedmypy icon indicating copy to clipboard operation
basedmypy copied to clipboard

f-string types

Open KotlinIsland opened this issue 1 year ago • 10 comments

def f[T: str](t: T) -> f"asdf{T}":
    return f"asdf{t}"

reveal_type(f("fdsa"))  # "asdffdsa"

KotlinIsland avatar Nov 07 '24 23:11 KotlinIsland

must T be restricted to str, or can it be anything that implements __format__, i.e. T: optype.CanFormat?

jorenham avatar Nov 09 '24 12:11 jorenham

hmmmmmm, ts only allows string | number | bigint | boolean | null | undefined, even though you can implement a toString, i'm not too sure what to do here...

KotlinIsland avatar Nov 09 '24 14:11 KotlinIsland

ehh, are we talking about python?

jorenham avatar Nov 09 '24 15:11 jorenham

>>> def f[T: str](t: T) -> f"asdf{T}":
...     return f"asdf{t}"
...     
>>> f.__annotations__
{'t': T, 'return': 'asdfT'}

maybe you can hack around this by monkeypatching TypeVar.__format__, so that it returns something like a DeferredFormat instance, instead of a str.

jorenham avatar Nov 09 '24 15:11 jorenham

ehh, are we talking about python?

yeah, just referencing prior art

I think ts kinda went with the idea of static string values are allowed, and when there is __str__, __repr__ and __format__ things can get really tricky, if we allow any of these then I suppose:

class A:
   def __format__(self, spec) -> f"A({str})": ...

def f(a: A) -> f"asdf {A}": # same as f"asdf A({str})"
    return f"asdf {a}"

does that mean:

class object:
    def __str__(self) -> f"<{self.__module__}.{self.__class__.__name__} object at {str}>"

KotlinIsland avatar Nov 10 '24 00:11 KotlinIsland

>>> def f[T: str](t: T) -> f"asdf{T}":
...     return f"asdf{t}"
...     
>>> f.__annotations__
{'t': T, 'return': 'asdfT'}

maybe you can hack around this by monkeypatching TypeVar.__format__, so that it returns something like a DeferredFormat instance, instead of a str.

don't think that would work for f"{str}"

KotlinIsland avatar Nov 10 '24 00:11 KotlinIsland

ehh, are we talking about python?

yeah, just referencing prior art

ah I must've read over the "ts" in the comment I was referencing there, mea culpa


don't think that would work for f"{str}"

Yea, good point...


if we allow any of these then I suppose:

class A:
   def __format__(self, spec) -> f"A({str})": ...

def f(a: A) -> f"asdf {A}": # same as f"asdf A({str})"
    return f"asdf {a}"

Yea I suppose that makes sense.

But now that I see this, I realize that this could indeed become very complicated. Mostly because the returned __format__ could depend on things that aren't known by the typechecker, such as private attributes that are not part of the (generic) type (i.e. as type-params or something). And then there's also the possibility that __format__ returns a subtype of str, e.g.

>>> class StringSpy(str):
...     def __str__(self):
...         return super().__str__() + " sabotage"
...     def __format__(self, spec):
...         return StringSpy(super().__format__(spec))
...         
>>> f"{StringSpy('nuclear reactor')}"
'nuclear reactor sabotage'
>>> type(_)
<class '__main__.StringSpy'>

So I can see why TS went about it the way they did. I guess that later on __format__ support could always be added. So I guess it might be easiest to start with just the common str-able builtin literals, and then see whether including __format__ is feasible after that.


does that mean:

class object:
    def __str__(self) -> f"<{self.__module__}.{self.__class__.__name__} object at {str}>"

self.__module__ isn't a type in this case 🤔


I haven't yet read it, but at first glance https://peps.python.org/pep-0750/ looks like it might be able to help with this.

jorenham avatar Nov 10 '24 01:11 jorenham

self.__module__ isn't a type in this case 🤔

  • #668

but that's nonsense anyway, that should return str

KotlinIsland avatar Nov 10 '24 02:11 KotlinIsland

self.__module__ isn't a type in this case 🤔

but that's nonsense anyway, that should return str

but shouldn't it then be

def __str__[ModuleT: str, ClassNameT: str](
    self: HasModule[ModuleT] & HasClass[HasName[ClassNameT]],
) -> f"<{ModuleT}.{ClassNameT} object at {str}>"

jorenham avatar Nov 10 '24 11:11 jorenham

no, it should be str, because it's subtypes can return any str

KotlinIsland avatar Nov 10 '24 14:11 KotlinIsland