basedmypy
basedmypy copied to clipboard
f-string types
def f[T: str](t: T) -> f"asdf{T}":
return f"asdf{t}"
reveal_type(f("fdsa")) # "asdffdsa"
must T be restricted to str, or can it be anything that implements __format__, i.e. T: optype.CanFormat?
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...
ehh, are we talking about python?
>>> 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.
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}>"
>>> 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 aDeferredFormatinstance, instead of astr.
don't think that would work for f"{str}"
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.
self.__module__isn't a type in this case 🤔
- #668
but that's nonsense anyway, that should return str
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}>"
no, it should be str, because it's subtypes can return any str