Ensure that GenericCallable is generated if typevars are present.#91
Ensure that GenericCallable is generated if typevars are present.#91
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
f6745ce to
a3cb55c
Compare
b957edd to
c6323c0
Compare
c6323c0 to
6988ccc
Compare
| ordinary: str | ||
| def foo(self: Self, a: int | None, *, b: int = ...) -> dict[str, int]: ... | ||
| def base[Z](self: Self, a: int | Z | None, b: ~K) -> dict[str, int | Z]: ... | ||
| def base[Z, K](self: Self, a: int | Z | None, b: ~K) -> dict[str, int | Z]: ... |
There was a problem hiding this comment.
Maybe we should take that K out of Final. It doesn't really make sense.
There was a problem hiding this comment.
other than the wacky formatting (K vs ~K), what would you expect this to look like?
| def member_method( | ||
| self, x: T | ||
| ) -> set[T] if IsAssignable[T, int] else T: ... |
There was a problem hiding this comment.
This case doesn't work in general, obviously, since one side could have a type var that the other doesn't.
We should at least document that somewhere.
I actually do have an insane plan that would allow solving this, involving repeatedly reexecuting the annotation with Bool et al rigged up to return different results, but Yury was horrified by the idea and we probably shouldn't do it.
There was a problem hiding this comment.
Another wild approach would be to read the bytecode and look for all globals that might be read.
| # - pattern matched annotations: T | None, set[T], etc. | ||
| # - type vars in an expression: U if IsAssignable[T, int] else V |
There was a problem hiding this comment.
The first one we definitely need to handle, if we are handling any cases at all.
The second one we can hopefully ignore? At least, the solutions are all crazy.
| for tv in _find_annotation_type_vars(raw_args, rr, globs): | ||
| if tv not in type_params: | ||
| type_params.append(tv) | ||
| rr = _eval_raw_annotations(raw_args, rr, globs) |
There was a problem hiding this comment.
I don't think I understand the algorithm here. Is the split where we try _find_annotation_type_vars before _eval_raw_annotations being done just to support the case with an if or for?
If so I think maybe we should skip doing that and just try processing the result of _eval_raw_annotations, since we can't make for/if work without doing some horrible things in general...
There was a problem hiding this comment.
The goal is to leave the bulk of the logic identical for get_annotations. At the same time, I break it into parts so that I am able to read the annotations that are used in intermediate computations. This is to find any type vars that were not declared by the function itself.
These new type vars are added to those declared by the function when generating the GenericCallable lambda.
There was a problem hiding this comment.
This has nothing to do with for/if in particular.
There was a problem hiding this comment.
OK, I guess the question here is (and I thought I half understood the answer): why can't we just call get_annotations and look for all the type variables in the result?
There was a problem hiding this comment.
The short answer is that at this point in get_local_defns, we call get_annotations, which may return None if it gets stuck. In that situation, there is no result to look into.
It gets stuck because the externally defined type var doesn't get substituted anywhere and so results in the type var itself.
We want to detect all the type vars while also avoiding the stuck exception. Some ways I thought about doing this:
- some sort of flag in the execution context that supresses stuck and then also store things in the context
- a different type of execution context entirely
- maybe looking at the annotate function globals
There was a problem hiding this comment.
Ok also, we may or may not need to cover external type vars that are in if/for, but I was more thinking that if any annotation has if/for, then this will occur, even if the external type var is not in some complex expression
eg.
T = typevar("T")
def f(self, x: T, y: int if IsAssignable[T,int] else str) -> T| return f | ||
|
|
||
|
|
||
| def _find_function_type_vars(ty, *, seen=None): |
There was a problem hiding this comment.
This probably should recurse, right? Through all typealiases
There was a problem hiding this comment.
Yes, but I wanted to keep this in sync with _eval_operators._collect_type_vars, which I didn't want to figure out yet.
6988ccc to
fcfe2c0
Compare
Currently, we don't properly generate GenericCallable if a typevar in the signature is from outside a class. For example:
Previously,
C.fwould be typed asCallable[Param[Literal["x"], T], T]while it should correctly be typed something likeGenericCallable[tuple[T], lambda T: Callable[Param[Literal["x"], T], T].