Compare commits

...

114 Commits

Author SHA1 Message Date
Carl Meyer
0fc9e5e0e9 Merge branch 'dcreager/explicit-constriants' into cjm/callable-return-fixed
* dcreager/explicit-constriants:
  update expected output for graph display test
  store this in constraint, not node
  track whether constraints are explicit or not
  track source_order in PathAssignments
  [ty] Use `title` for configuration code fences in ty reference documentation (#21992)
2025-12-15 19:50:48 -08:00
Carl Meyer
7f7fb50a43 update expected output for graph display test 2025-12-15 18:06:47 -08:00
Douglas Creager
0b6bd9a735 store this in constraint, not node 2025-12-15 20:18:13 -05:00
Douglas Creager
c790aa7474 track whether constraints are explicit or not 2025-12-15 20:13:21 -05:00
Douglas Creager
298e2dcb4e track source_order in PathAssignments 2025-12-15 20:10:53 -05:00
Douglas Creager
44256deae3 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main:
  [ty] Consistent ordering of constraint set specializations, take 2 (#21983)
  [ty] Remove invalid statement-keyword completions in for-statements (#21979)
  [ty] Avoid caching trivial is-redundant-with calls (#21989)
2025-12-15 14:57:34 -05:00
Douglas Creager
06a02fc46e Revert "fix py-fuzzer test failure"
This reverts commit b9ecab1f24.
2025-12-15 12:26:33 -05:00
Douglas Creager
2897d498fd add TODO 2025-12-15 12:13:03 -05:00
Douglas Creager
4e3dd58815 fix Class3 example 2025-12-15 12:07:32 -05:00
Douglas Creager
26c847c229 add a bunch of callable reveals 2025-12-15 12:01:40 -05:00
Douglas Creager
2614be36cc add note about paramspec overloads 2025-12-15 12:01:40 -05:00
Douglas Creager
a914071640 return a slice! 2025-12-15 12:01:40 -05:00
Douglas Creager
483f34207b Revert "add canonically_ordered"
This reverts commit ddcd76c544.
2025-12-15 12:01:19 -05:00
Douglas Creager
42185b643b Revert "canonical ordering for constraint set mappings"
This reverts commit 3c811c19d4.
2025-12-15 12:00:32 -05:00
Douglas Creager
94b4dd86c0 track source_order in PathAssignments 2025-12-15 11:59:09 -05:00
Douglas Creager
dfcdbcffec Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main:
  Fluent formatting of method chains (#21369)
  [ty] Avoid stack overflow when calculating inferable typevars (#21971)
  [ty] Add "qualify ..." code fix for undefined references (#21968)
  [ty] Use jemalloc on linux (#21975)
  Update MSRV to 1.90 (#21987)
  [ty] Improve check enforcing that an overloaded function must have an implementation (#21978)
  Update actions/checkout digest to 8e8c483 (#21982)
  [ty] Use `ParamSpec` without the attr for inferable check (#21934)
  [ty] Emit diagnostic when a type variable with a default is followed by one without a default (#21787)
2025-12-15 11:06:49 -05:00
Douglas Creager
f8a5d04296 Merge branch 'dcreager/source-order-constraints' into dcreager/callable-return
* dcreager/source-order-constraints: (30 commits)
  clippy
  fix test expectations (again)
  include source_order in display_graph output
  place bounds/constraints first
  don't always bump
  only fold once
  document display source_order
  more comments
  remove now-unused items
  fix test expectation
  use source order in specialize_constrained too
  document overall approach
  more comment
  reuse self source_order
  sort specialize_constrained by source_order
  lots of renaming
  remove source_order_for
  simpler source_order_for
  doc
  restore TODOs
  ...
2025-12-15 10:53:25 -05:00
Douglas Creager
cba45acd86 clippy 2025-12-15 10:23:37 -05:00
Douglas Creager
1dd3cf0e58 fix test expectations (again) 2025-12-15 10:22:27 -05:00
Douglas Creager
d9429754b9 include source_order in display_graph output 2025-12-15 10:19:58 -05:00
Douglas Creager
7f4893d200 place bounds/constraints first 2025-12-15 10:16:06 -05:00
Douglas Creager
88eb5eba22 don't always bump 2025-12-15 10:15:04 -05:00
Douglas Creager
63c75d85d0 only fold once 2025-12-15 09:55:17 -05:00
Douglas Creager
358185b5e2 document display source_order 2025-12-15 09:09:41 -05:00
Douglas Creager
019db2a22e more comments 2025-12-15 08:54:19 -05:00
Douglas Creager
ccb03d3b23 remove now-unused items 2025-12-15 08:40:45 -05:00
Douglas Creager
da31e138b4 fix test expectation 2025-12-15 08:39:21 -05:00
Douglas Creager
7e2ea8bd69 use source order in specialize_constrained too 2025-12-15 08:35:50 -05:00
Douglas Creager
1f34f43745 document overall approach 2025-12-14 21:57:35 -05:00
Douglas Creager
649c7bce58 more comment 2025-12-14 21:51:56 -05:00
Douglas Creager
92894d3712 reuse self source_order 2025-12-14 21:49:50 -05:00
Douglas Creager
5a8a9500b9 sort specialize_constrained by source_order 2025-12-14 19:38:07 -05:00
Douglas Creager
49ca97a20e lots of renaming 2025-12-14 19:01:44 -05:00
Douglas Creager
d223f64af1 remove source_order_for 2025-12-14 18:56:22 -05:00
Douglas Creager
a4a3aff8d6 simpler source_order_for 2025-12-14 18:47:45 -05:00
Douglas Creager
bdaf8e5812 doc 2025-12-14 13:19:34 -05:00
Douglas Creager
e583cb7682 restore TODOs 2025-12-14 13:11:31 -05:00
Douglas Creager
86271d605d codex 2 2025-12-14 13:10:51 -05:00
Douglas Creager
8655598901 codex attempt 1 2025-12-14 12:56:21 -05:00
Douglas Creager
b9ecab1f24 fix py-fuzzer test failure 2025-12-13 20:21:50 -05:00
Douglas Creager
3c811c19d4 canonical ordering for constraint set mappings 2025-12-13 20:05:49 -05:00
Douglas Creager
ddcd76c544 add canonically_ordered 2025-12-13 20:03:15 -05:00
Alex Waygood
8871fddaf9 bump expected sympy diagnostics in benchmark 2025-12-13 15:22:37 +00:00
Douglas Creager
8069064aca Merge remote-tracking branch 'origin/dcreager/callable-return' into dcreager/callable-return
* origin/dcreager/callable-return:
  bump expected diagnostics for static-frame
2025-12-12 22:26:38 -05:00
Douglas Creager
068eb1f500 add sig todo 2025-12-12 22:22:33 -05:00
Douglas Creager
e906526578 only when function defs are same 2025-12-12 22:22:33 -05:00
Douglas Creager
25a6690cdb add materialization test 2025-12-12 22:22:33 -05:00
Douglas Creager
99ec0be478 fix test 2025-12-12 22:22:33 -05:00
Douglas Creager
c94fbe20a2 Merge remote-tracking branch 'origin/main' into gggg
* origin/main: (22 commits)
  [ty] Allow gradual lower/upper bounds in a constraint set (#21957)
  [ty] disallow explicit specialization of type variables themselves (#21938)
  [ty] Improve diagnostics for unsupported binary operations and unsupported augmented assignments (#21947)
  [ty] update implicit root docs (#21955)
  [ty] Enable even more goto-definition on inlay hints (#21950)
  Document known lambda formatting deviations from Black (#21954)
  [ty] fix hover type on named expression target (#21952)
  Bump benchmark dependencies (#21951)
  Keep lambda parameters on one line and parenthesize the body if it expands (#21385)
  [ty] Improve resolution of absolute imports in tests (#21817)
  [ty] Support `__all__ += submodule.__all__`
  [ty] Change frequency of invalid `__all__` debug message
  [ty] Add `KnownUnion::to_type()` (#21948)
  [ty] Classify `cls` as class parameter (#21944)
  [ty] Stabilize rename (#21940)
  [ty] Ignore `__all__` for document and workspace symbol requests
  [ty] Attach db to background request handler task (#21941)
  [ty] Fix outdated version in publish diagnostics after `didChange` (#21943)
  [ty] avoid fixpoint unioning of types containing current-cycle Divergent (#21910)
  [ty] improve bad specialization results & error messages (#21840)
  ...
2025-12-12 22:22:11 -05:00
Douglas Creager
690310cea3 not needed anymore 2025-12-12 13:05:29 -05:00
Douglas Creager
e476624ef2 never? 2025-12-12 13:05:29 -05:00
Douglas Creager
2fd7a7d944 limit to valid specializations 2025-12-12 13:05:29 -05:00
Douglas Creager
2950af4fd9 calculate variance from parameter type 2025-12-12 13:05:29 -05:00
Douglas Creager
73acf0a926 whelp those are backwards 2025-12-12 13:05:25 -05:00
Douglas Creager
c85f102e70 no really 2025-12-12 12:52:46 -05:00
Douglas Creager
4bcca58c3a add mapping for lower bound too 2025-12-12 12:52:13 -05:00
Carl Meyer
c6a4e1c8ad bump expected diagnostics for static-frame 2025-12-11 15:11:42 -08:00
Douglas Creager
f624bfdf63 clean up the diff 2025-12-11 16:21:30 -05:00
Douglas Creager
bfde3e41a7 update tests 2025-12-11 16:09:33 -05:00
Douglas Creager
a892be3124 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: (36 commits)
  [ty] Defer all parameter and return type annotations (#21906)
  [ty] Fix workspace symbols to return members too (#21926)
  Document range suppressions, reorganize suppression docs (#21884)
  Ignore ruff:isort like ruff:noqa in new suppressions (#21922)
  [ty] Handle `Definition`s in `SemanticModel::scope` (#21919)
  [ty] Attach salsa db when running ide tests for easier debugging (#21917)
  [ty] Don't show hover for expressions with no inferred type (#21924)
  [ty] avoid unions of generic aliases of the same class in fixpoint (#21909)
  [ty] Squash false positive logs for failing to find `builtins` as a real module
  [ty] Uniformly use "not supported" in diagnostics (#21916)
  [ty] Reduce size of ty-ide snapshots (#21915)
  [ty] Adjust scope completions to use all reachable symbols
  [ty] Rename `all_members_of_scope` to `all_end_of_scope_members`
  [ty] Remove `all_` prefix from some routines on UseDefMap
  Enable `--document-private-items` for `ruff_python_formatter` (#21903)
  Remove `BackwardsTokenizer` based `parenthesized_range` references in `ruff_linter` (#21836)
  [ty] Revert "Do not infer types for invalid binary expressions in annotations" (#21914)
  Skip over trivia tokens after re-lexing (#21895)
  [ty] Avoid inferring types for invalid binary expressions in string annotations (#21911)
  [ty] Improve overload call resolution tracing (#21913)
  ...
2025-12-11 16:06:59 -05:00
Douglas Creager
b1ede8885b add more comments 2025-12-09 20:33:07 -05:00
Douglas Creager
4f7ad7bbc9 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: (33 commits)
  [ty] Simplify union lower bounds and intersection upper bounds in constraint sets (#21871)
  [ty] Collapse `never` paths in constraint set BDDs (#21880)
  Fix leading comment formatting for lambdas with multiple parameters (#21879)
  [ty] Type inference for `@asynccontextmanager` (#21876)
  Fix comment placement in lambda parameters (#21868)
  [`pylint`] Detect subclasses of builtin exceptions (`PLW0133`) (#21382)
  Fix stack overflow with recursive generic protocols (depth limit) (#21858)
  New diagnostics for unused range suppressions (#21783)
  [ty] Use default settings in completion tests
  [ty] Infer type variables within generic unions  (#21862)
  [ty] Fix overload filtering to prefer more "precise" match (#21859)
  [ty] Stabilize auto-import
  [ty] Fix reveal-type E2E test (#21865)
  [ty] Use concise message for LSP clients not supporting related diagnostic information (#21850)
  Include more details in Tokens 'offset is inside token' panic message (#21860)
  apply range suppressions to filter diagnostics (#21623)
  [ty] followup: add-import action for `reveal_type` too (#21668)
  [ty] Enrich function argument auto-complete suggestions with annotated types
  [ty] Add autocomplete suggestions for function arguments
  [`flake8-bugbear`] Accept immutable slice default arguments (`B008`) (#21823)
  ...
2025-12-09 19:50:47 -05:00
Douglas Creager
9a3786179d skip current type when specializing 2025-12-09 10:49:05 -05:00
Douglas Creager
f82b3f1eff abstract over any mention of a typevar 2025-12-09 08:38:32 -05:00
Douglas Creager
f23ae75b5d group typevars by binding context 2025-12-08 19:14:57 -05:00
Douglas Creager
f29200c789 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main:
  [ty] Add test case for fixed panic (#21832)
  [ty] Avoid double-analyzing tuple in `Final` subscript (#21828)
  [flake8-bandit] Fix false positive when using non-standard `CSafeLoader` path (S506). (#21830)
  Add minimal-size build profile (#21826)
2025-12-07 14:57:55 -05:00
Douglas Creager
72e0c32a99 clippy 2025-12-07 14:51:21 -05:00
Douglas Creager
81fc51e197 update test TODOs 2025-12-07 14:46:52 -05:00
Douglas Creager
b3e4855230 any here 2025-12-07 14:44:52 -05:00
Douglas Creager
c56d5cc24b not failing anymore 2025-12-07 14:39:18 -05:00
Douglas Creager
22c7fc4516 don't pivot on never or object 2025-12-07 14:38:34 -05:00
Douglas Creager
ecb9c1301b gotta get those return types too 2025-12-07 14:38:34 -05:00
Douglas Creager
c60560f39d do this at the overloads level 2025-12-05 18:41:37 -05:00
Douglas Creager
61381522e4 Revert "skip non-inferable"
This reverts commit 94aca37ca8.
2025-12-05 18:05:33 -05:00
Douglas Creager
d47e9a60df callable invariance rears its head again 2025-12-05 16:41:12 -05:00
Douglas Creager
a372e63b2c different TODO explanation for overload example 2025-12-05 16:33:29 -05:00
Douglas Creager
b84a35f22f oh hey that's a real bug 2025-12-05 16:03:28 -05:00
Douglas Creager
657685f731 don't throw away return type 2025-12-05 15:57:47 -05:00
Douglas Creager
056258c767 cs assignability for paramspecs 2025-12-05 15:48:52 -05:00
Douglas Creager
db488e3cf7 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main:
  [ty] Allow `tuple[Any, ...]` to assign to `tuple[int, *tuple[int, ...]]` (#21803)
  [ty] Support renaming import aliases (#21792)
  [ty] Add redeclaration LSP tests (#21812)
  [ty] more detailed description of "Size limit on unions of literals" in mdtest (#21804)
  [ty] Complete support for `ParamSpec` (#21445)
  [ty] Update benchmark dependencies (#21815)
2025-12-05 15:39:40 -05:00
Douglas Creager
c74eb12db4 pull this out into a helper method 2025-12-05 10:00:47 -05:00
Douglas Creager
c0dc6cfa61 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: (41 commits)
  [ty] Carry generic context through when converting class into `Callable` (#21798)
  [ty] Add more tests for renamings (#21810)
  [ty] Minor improvements to `assert_type` diagnostics (#21811)
  [ty] Add some attribute/method renaming test cases (#21809)
  Update mkdocs-material to 9.7.0 (Insiders now free) (#21797)
  Remove unused whitespaces in test cases (#21806)
  [ty] fix panic when instantiating a type variable with invalid constraints (#21663)
  [ty] fix build failure caused by conflicts between #21683 and #21800 (#21802)
  [ty] do nothing with `store_expression_type` if `inner_expression_inference_state` is `Get` (#21718)
  [ty] increase the limit on the number of elements in a non-recursively defined literal union (#21683)
  [ty] normalize typevar bounds/constraints in cycles (#21800)
  [ty] Update completion eval to include modules
  [ty] Add modules to auto-import
  [ty] Add support for module-only import requests
  [ty] Refactor auto-import symbol info
  [ty] Clarify the use of `SymbolKind` in auto-import
  [ty] Redact ranking of completions from e2e LSP tests
  [ty] Tweaks tests to use clearer language
  [ty] Update evaluation results
  [ty] Make auto-import ignore symbols in modules starting with a `_`
  ...
2025-12-05 09:00:54 -05:00
Douglas Creager
8c7e20abd6 format, really?!?! 2025-12-04 09:55:08 -05:00
Douglas Creager
3384392747 treat each overload separately 2025-12-04 09:48:20 -05:00
Douglas Creager
54a4f2ec58 use ConstraintSetAssignability for constraint bounds 2025-12-04 09:48:20 -05:00
Douglas Creager
b314119835 catch self-referential typevars 2025-12-03 20:04:24 -05:00
Douglas Creager
1e33d25d1c fix test 2025-12-03 16:38:01 -05:00
Douglas Creager
b90cdfc2f7 generic 2025-12-03 16:36:21 -05:00
Douglas Creager
94aca37ca8 skip non-inferable 2025-12-03 16:30:44 -05:00
Douglas Creager
75e9d66d4b self 2025-12-03 12:37:04 -05:00
Douglas Creager
3bcca62472 doc 2025-12-03 12:12:00 -05:00
Douglas Creager
85e6143e07 use self annotation in synthesized __init__ callable 2025-12-03 12:09:04 -05:00
Douglas Creager
77ce24a5bf allow multiple overloads/callables when inferring 2025-12-03 12:04:59 -05:00
Douglas Creager
db5834dfd7 add failing tests 2025-12-03 12:04:00 -05:00
Douglas Creager
2e46c8de06 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main:
  [ty] Reachability constraints: minor documentation fixes (#21774)
  [ty] Fix non-determinism in `ConstraintSet.specialize_constrained` (#21744)
  [ty] Improve `@override`, `@final` and Liskov checks in cases where there are multiple reachable definitions (#21767)
  [ty] Extend `invalid-explicit-override` to also cover properties decorated with `@override` that do not override anything (#21756)
  [ty] Enable LRU collection for parsed module (#21749)
  [ty] Support typevar-specialized dynamic types in generic type aliases (#21730)
  Add token based `parenthesized_ranges` implementation (#21738)
  [ty] Default-specialization of generic type aliases (#21765)
  [ty] Suppress false positives when `dataclasses.dataclass(...)(cls)` is called imperatively (#21729)
  [syntax-error] Default type parameter followed by non-default type parameter (#21657)
2025-12-03 10:48:36 -05:00
Douglas Creager
d3fd988337 fix tests 2025-12-02 21:49:03 -05:00
Douglas Creager
a0f64bd0ae even more hack 2025-12-02 21:41:55 -05:00
Douglas Creager
beb2956a14 carry over failing test from conformance suite 2025-12-02 21:32:02 -05:00
Douglas Creager
58c67fd4cd don't create T ≤ T constraints 2025-12-02 19:01:08 -05:00
Douglas Creager
a303b7a8aa Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main:
  new module for parsing ranged suppressions (#21441)
  [ty] `type[T]` is assignable to an inferable typevar (#21766)
  Fix syntax error false positives for `await` outside functions (#21763)
  [ty] Improve diagnostics for unsupported comparison operations (#21737)
2025-12-02 18:42:43 -05:00
Douglas Creager
30452586ad clippity bippity 2025-12-02 18:27:16 -05:00
Douglas Creager
7bbf839325 hackity hack 2025-12-02 18:24:15 -05:00
Douglas Creager
957304ec15 mdlint 2025-12-02 15:40:43 -05:00
Douglas Creager
d88120b187 mark these as TODO 2025-12-02 14:46:29 -05:00
Douglas Creager
2b949b3e67 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: (67 commits)
  Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760)
  [ty] Don't confuse multiple occurrences of `typing.Self` when binding bound methods (#21754)
  Use our org-wide Renovate preset (#21759)
  Delete `my-script.py` (#21751)
  [ty] Move `all_members`, and related types/routines, out of `ide_support.rs` (#21695)
  [ty] Fix find-references for import aliases (#21736)
  [ty] add tests for workspaces (#21741)
  [ty] Stop testing the (brittle) constraint set display implementation (#21743)
  [ty] Use generator over list comprehension to avoid cast (#21748)
  [ty] Add a diagnostic for prohibited `NamedTuple` attribute overrides (#21717)
  [ty] Fix subtyping with `type[T]` and unions (#21740)
  Use `npm ci --ignore-scripts` everywhere (#21742)
  [`flake8-simplify`] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (`SIM222`, `SIM223`) (#21479)
  [`flake8-use-pathlib`] Mark fixes unsafe for return type changes (`PTH104`, `PTH105`, `PTH109`, `PTH115`) (#21440)
  [ty] Fix auto-import code action to handle pre-existing import
  Enable PEP 740 attestations when publishing to PyPI (#21735)
  [ty] Fix find references for type defined in stub (#21732)
  Use OIDC instead of codspeed token (#21719)
  [ty] Exclude `typing_extensions` from completions unless it's really available
  [ty] Fix false positives for `class F(Generic[*Ts]): ...` (#21723)
  ...
2025-12-02 14:23:15 -05:00
Douglas Creager
2c6267436f clean up the diff 2025-11-26 18:35:15 -05:00
Douglas Creager
fedc75463b this gets recursively expanded now 2025-11-26 18:35:15 -05:00
Douglas Creager
9950c126fe these need to be positional only to be assignable 2025-11-26 18:35:15 -05:00
Douglas Creager
b7fb6797b4 it works! 2025-11-26 18:35:15 -05:00
Douglas Creager
fc2f17508b use constraint set assignable 2025-11-26 18:35:15 -05:00
Douglas Creager
20ecb561bb add ConstraintSetAssignability relation 2025-11-26 18:35:15 -05:00
Douglas Creager
3b509e9015 it's a start 2025-11-26 18:35:15 -05:00
Douglas Creager
998b20f078 add for_each_path 2025-11-26 18:35:15 -05:00
Douglas Creager
544dafa66e add more sequents 2025-11-26 18:35:15 -05:00
19 changed files with 1013 additions and 246 deletions

View File

@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13030,
13100,
);
static TANJUN: Benchmark = Benchmark::new(
@@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
950,
1100,
);
#[track_caller]

View File

@@ -194,7 +194,7 @@ reveal_type(B().name_does_not_matter()) # revealed: B
reveal_type(B().positional_only(1)) # revealed: B
reveal_type(B().keyword_only(x=1)) # revealed: B
# TODO: This should deally be `B`
reveal_type(B().decorated_method()) # revealed: Unknown
reveal_type(B().decorated_method()) # revealed: Self@decorated_method
reveal_type(B().a_property) # revealed: B

View File

@@ -43,9 +43,7 @@ async def main():
loop = asyncio.get_event_loop()
with concurrent.futures.ThreadPoolExecutor() as pool:
result = await loop.run_in_executor(pool, blocking_function)
# TODO: should be `int`
reveal_type(result) # revealed: Unknown
reveal_type(result) # revealed: int
```
### `asyncio.Task`

View File

@@ -82,8 +82,7 @@ def get_default() -> str:
reveal_type(field(default=1)) # revealed: dataclasses.Field[Literal[1]]
reveal_type(field(default=None)) # revealed: dataclasses.Field[None]
# TODO: this could ideally be `dataclasses.Field[str]` with a better generics solver
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[Unknown]
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[str]
```
## dataclass_transform field_specifiers

View File

@@ -144,11 +144,10 @@ from functools import cache
def f(x: int) -> int:
return x**2
# TODO: Should be `_lru_cache_wrapper[int]`
reveal_type(f) # revealed: _lru_cache_wrapper[Unknown]
# TODO: Should be `int`
reveal_type(f(1)) # revealed: Unknown
# revealed: _lru_cache_wrapper[int]
reveal_type(f)
# revealed: int
reveal_type(f(1))
```
## Lambdas as decorators

View File

@@ -11,9 +11,9 @@ classes. Uses of these items should subsequently produce a warning.
from typing_extensions import deprecated
@deprecated("use OtherClass")
def myfunc(): ...
def myfunc(x: int): ...
myfunc() # error: [deprecated] "use OtherClass"
myfunc(1) # error: [deprecated] "use OtherClass"
```
```py

View File

@@ -555,8 +555,7 @@ def identity(x: T) -> T:
def head(xs: list[T]) -> T:
return xs[0]
# TODO: this should be `Literal[1]`
reveal_type(invoke(identity, 1)) # revealed: Unknown
reveal_type(invoke(identity, 1)) # revealed: Literal[1]
# TODO: this should be `Unknown | int`
reveal_type(invoke(head, [1, 2, 3])) # revealed: Unknown

View File

@@ -518,8 +518,7 @@ V = TypeVar("V", default="V")
class D(Generic[V]):
x: V
# TODO: we shouldn't leak a typevar like this in type inference
reveal_type(D().x) # revealed: V@D
reveal_type(D().x) # revealed: Unknown
```
## Regression

View File

@@ -493,8 +493,7 @@ def identity[T](x: T) -> T:
def head[T](xs: list[T]) -> T:
return xs[0]
# TODO: this should be `Literal[1]`
reveal_type(invoke(identity, 1)) # revealed: Unknown
reveal_type(invoke(identity, 1)) # revealed: Literal[1]
# TODO: this should be `Unknown | int`
reveal_type(invoke(head, [1, 2, 3])) # revealed: Unknown
@@ -736,3 +735,159 @@ def f[T](x: T, y: Not[T]) -> T:
y = x # error: [invalid-assignment]
return x
```
## `Callable` parameters
We can recurse into the parameters and return values of `Callable` parameters to infer
specializations of a generic function.
```py
from typing import Any, Callable, NoReturn, overload, Self
from ty_extensions import generic_context, into_callable
def accepts_callable[**P, R](callable: Callable[P, R]) -> Callable[P, R]:
return callable
def returns_int() -> int:
raise NotImplementedError
# revealed: () -> int
reveal_type(into_callable(returns_int))
# revealed: () -> int
reveal_type(accepts_callable(returns_int))
# revealed: int
reveal_type(accepts_callable(returns_int)())
class ClassWithoutConstructor: ...
# revealed: () -> ClassWithoutConstructor
reveal_type(into_callable(ClassWithoutConstructor))
# revealed: () -> ClassWithoutConstructor
reveal_type(accepts_callable(ClassWithoutConstructor))
# revealed: ClassWithoutConstructor
reveal_type(accepts_callable(ClassWithoutConstructor)())
class ClassWithNew:
def __new__(cls, *args, **kwargs) -> Self:
raise NotImplementedError
# revealed: (...) -> ClassWithNew
reveal_type(into_callable(ClassWithNew))
# revealed: (...) -> ClassWithNew
reveal_type(accepts_callable(ClassWithNew))
# revealed: ClassWithNew
reveal_type(accepts_callable(ClassWithNew)())
class ClassWithInit:
def __init__(self) -> None: ...
# revealed: () -> ClassWithInit
reveal_type(into_callable(ClassWithInit))
# revealed: () -> ClassWithInit
reveal_type(accepts_callable(ClassWithInit))
# revealed: ClassWithInit
reveal_type(accepts_callable(ClassWithInit)())
class ClassWithNewAndInit:
def __new__(cls, *args, **kwargs) -> Self:
raise NotImplementedError
def __init__(self, x: int) -> None: ...
# TODO: We do not currently solve a common behavioral supertype for the two solutions of P.
# revealed: ((...) -> ClassWithNewAndInit) | ((x: int) -> ClassWithNewAndInit)
reveal_type(into_callable(ClassWithNewAndInit))
# TODO: revealed: ((...) -> ClassWithNewAndInit) | ((x: int) -> ClassWithNewAndInit)
# revealed: (...) -> ClassWithNewAndInit
reveal_type(accepts_callable(ClassWithNewAndInit))
# revealed: ClassWithNewAndInit
reveal_type(accepts_callable(ClassWithNewAndInit)())
class Meta(type):
def __call__(cls, *args: Any, **kwargs: Any) -> NoReturn:
raise NotImplementedError
class ClassWithNoReturnMetatype(metaclass=Meta):
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
raise NotImplementedError
# TODO: The return types here are wrong, because we end up creating a constraint (Never ≤ R), which
# we confuse with "R has no lower bound".
# revealed: (...) -> Never
reveal_type(into_callable(ClassWithNoReturnMetatype))
# TODO: revealed: (...) -> Never
# revealed: (...) -> Unknown
reveal_type(accepts_callable(ClassWithNoReturnMetatype))
# TODO: revealed: Never
# revealed: Unknown
reveal_type(accepts_callable(ClassWithNoReturnMetatype)())
class Proxy: ...
class ClassWithIgnoredInit:
def __new__(cls) -> Proxy:
return Proxy()
def __init__(self, x: int) -> None: ...
# revealed: () -> Proxy
reveal_type(into_callable(ClassWithIgnoredInit))
# revealed: () -> Proxy
reveal_type(accepts_callable(ClassWithIgnoredInit))
# revealed: Proxy
reveal_type(accepts_callable(ClassWithIgnoredInit)())
class ClassWithOverloadedInit[T]:
t: T # invariant
@overload
def __init__(self: "ClassWithOverloadedInit[int]", x: int) -> None: ...
@overload
def __init__(self: "ClassWithOverloadedInit[str]", x: str) -> None: ...
def __init__(self, x: int | str) -> None: ...
# TODO: The old solver cannot handle this overloaded constructor. The ideal solution is that we
# would solve **P once, and map it to the entire overloaded signature of the constructor. This
# mapping would have to include the return types, since there are different return types for each
# overload. We would then also have to determine that R must be equal to the return type of **P's
# solution.
# revealed: Overload[(x: int) -> ClassWithOverloadedInit[int], (x: str) -> ClassWithOverloadedInit[str]]
reveal_type(into_callable(ClassWithOverloadedInit))
# TODO: revealed: Overload[(x: int) -> ClassWithOverloadedInit[int], (x: str) -> ClassWithOverloadedInit[str]]
# revealed: Overload[(x: int) -> ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str], (x: str) -> ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]]
reveal_type(accepts_callable(ClassWithOverloadedInit))
# TODO: revealed: ClassWithOverloadedInit[int]
# revealed: ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]
reveal_type(accepts_callable(ClassWithOverloadedInit)(0))
# TODO: revealed: ClassWithOverloadedInit[str]
# revealed: ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]
reveal_type(accepts_callable(ClassWithOverloadedInit)(""))
class GenericClass[T]:
t: T # invariant
def __new__(cls, x: list[T], y: list[T]) -> Self:
raise NotImplementedError
def _(x: list[str]):
# TODO: This fails because we are not propagating GenericClass's generic context into the
# Callable that we create for it.
# revealed: (x: list[T@GenericClass], y: list[T@GenericClass]) -> GenericClass[T@GenericClass]
reveal_type(into_callable(GenericClass))
# revealed: ty_extensions.GenericContext[T@GenericClass]
reveal_type(generic_context(into_callable(GenericClass)))
# revealed: (x: list[T@GenericClass], y: list[T@GenericClass]) -> GenericClass[T@GenericClass]
reveal_type(accepts_callable(GenericClass))
# TODO: revealed: ty_extensions.GenericContext[T@GenericClass]
# revealed: None
reveal_type(generic_context(accepts_callable(GenericClass)))
# TODO: revealed: GenericClass[str]
# TODO: no errors
# revealed: GenericClass[T@GenericClass]
# error: [invalid-argument-type]
# error: [invalid-argument-type]
reveal_type(accepts_callable(GenericClass)(x, x))
```

View File

@@ -503,7 +503,8 @@ class C[**P]:
def __init__(self, f: Callable[P, int]) -> None:
self.f = f
def f(x: int, y: str) -> bool:
# Note that the return type must match exactly, since C is invariant on the return type of C.f.
def f(x: int, y: str) -> int:
return True
c = C(f)
@@ -618,6 +619,22 @@ reveal_type(foo.method) # revealed: bound method Foo[(int, str, /)].method(int,
reveal_type(foo.method(1, "a")) # revealed: str
```
### Gradual types propagate through `ParamSpec` inference
```py
from typing import Callable
def callable_identity[**P, R](func: Callable[P, R]) -> Callable[P, R]:
return func
@callable_identity
def f(env: dict) -> None:
pass
# revealed: (env: dict[Unknown, Unknown]) -> None
reveal_type(f)
```
### Overloads
`overloaded.pyi`:
@@ -662,7 +679,7 @@ reveal_type(change_return_type(int_int)) # revealed: Overload[(x: int) -> str,
reveal_type(change_return_type(int_str)) # revealed: Overload[(x: int) -> str, (x: str) -> str]
# error: [invalid-argument-type]
reveal_type(change_return_type(str_str)) # revealed: Overload[(x: int) -> str, (x: str) -> str]
reveal_type(change_return_type(str_str)) # revealed: (...) -> str
# TODO: Both of these shouldn't raise an error
# error: [invalid-argument-type]

View File

@@ -883,7 +883,7 @@ reveal_type(C[int]().y) # revealed: int
class D[T = T]:
x: T
reveal_type(D().x) # revealed: T@D
reveal_type(D().x) # revealed: Unknown
```
[pep 695]: https://peps.python.org/pep-0695/

View File

@@ -426,7 +426,8 @@ from ty_extensions import ConstraintSet, generic_context
def mentions[T, U]():
# (T@mentions ≤ int) ∧ (U@mentions = list[T@mentions])
constraints = ConstraintSet.range(Never, T, int) & ConstraintSet.range(list[T], U, list[T])
# revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = list[int]]
# TODO: revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = list[int]]
# revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = Unknown]
reveal_type(generic_context(mentions).specialize_constrained(constraints))
```

View File

@@ -304,7 +304,7 @@ x11: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
reveal_type(x11) # revealed: list[Literal[1, 2, 3]]
x12: Y[Y[Literal[1]]] = [[1]]
reveal_type(x12) # revealed: list[Y[Literal[1]]]
reveal_type(x12) # revealed: list[list[Literal[1]]]
x13: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
reveal_type(x13) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]

View File

@@ -15,9 +15,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md
1 | from typing_extensions import deprecated
2 |
3 | @deprecated("use OtherClass")
4 | def myfunc(): ...
4 | def myfunc(x: int): ...
5 |
6 | myfunc() # error: [deprecated] "use OtherClass"
6 | myfunc(1) # error: [deprecated] "use OtherClass"
7 | from typing_extensions import deprecated
8 |
9 | @deprecated("use BetterClass")
@@ -42,9 +42,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md
warning[deprecated]: The function `myfunc` is deprecated
--> src/mdtest_snippet.py:6:1
|
4 | def myfunc(): ...
4 | def myfunc(x: int): ...
5 |
6 | myfunc() # error: [deprecated] "use OtherClass"
6 | myfunc(1) # error: [deprecated] "use OtherClass"
| ^^^^^^ use OtherClass
7 | from typing_extensions import deprecated
|

View File

@@ -918,16 +918,33 @@ impl<'db> Type<'db> {
previous: Self,
cycle: &salsa::Cycle,
) -> Self {
// Avoid unioning two generic aliases of the same class together; this union will never
// simplify and is likely to cause downstream problems. This introduces the theoretical
// possibility of cycle oscillation involving such types (because we are not strictly
// widening the type on each iteration), but so far we have not seen an example of that.
// When we encounter a salsa cycle, we want to avoid oscillating between two or more types
// without converging on a fixed-point result. Most of the time, we union together the
// types from each cycle iteration to ensure that our result is monotonic, even if we
// encounter oscillation.
//
// However, there are a couple of cases where we don't want to do that, and want to use the
// later cycle iteration's result directly. This introduces the theoretical possibility of
// cycle oscillation involving such types (because we are not strictly widening the type on
// each iteration), but so far we have not seen an example of that.
match (previous, self) {
// Avoid unioning two generic aliases of the same class together; this union will never
// simplify and is likely to cause downstream problems.
(Type::GenericAlias(prev_alias), Type::GenericAlias(curr_alias))
if prev_alias.origin(db) == curr_alias.origin(db) =>
{
self
}
// Similarly, don't union together two function literals, since there are several parts
// of our type inference machinery that assume that we infer a single FunctionLiteral
// type for each overload of each function definition.
(Type::FunctionLiteral(prev_function), Type::FunctionLiteral(curr_function))
if prev_function.definition(db) == curr_function.definition(db) =>
{
self
}
_ => {
// Also avoid unioning in a previous type which contains a Divergent from the
// current cycle, if the most-recent type does not. This cannot cause an
@@ -1843,7 +1860,7 @@ impl<'db> Type<'db> {
}
}
Type::ClassLiteral(class_literal) => {
Some(class_literal.default_specialization(db).into_callable(db))
Some(class_literal.identity_specialization(db).into_callable(db))
}
Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),
@@ -1975,6 +1992,16 @@ impl<'db> Type<'db> {
.is_always_satisfied(db)
}
/// Return true if this type is assignable to type `target` using constraint-set assignability.
///
/// This uses `TypeRelation::ConstraintSetAssignability`, which encodes typevar relations into
/// a constraint set and lets `satisfied_by_all_typevars` perform existential vs universal
/// reasoning depending on inferable typevars.
pub fn is_constraint_set_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
self.when_constraint_set_assignable_to(db, target, InferableTypeVars::None)
.is_always_satisfied(db)
}
fn when_assignable_to(
self,
db: &'db dyn Db,
@@ -1984,6 +2011,20 @@ impl<'db> Type<'db> {
self.has_relation_to(db, target, inferable, TypeRelation::Assignability)
}
fn when_constraint_set_assignable_to(
self,
db: &'db dyn Db,
target: Type<'db>,
inferable: InferableTypeVars<'_, 'db>,
) -> ConstraintSet<'db> {
self.has_relation_to(
db,
target,
inferable,
TypeRelation::ConstraintSetAssignability,
)
}
/// Return `true` if it would be redundant to add `self` to a union that already contains `other`.
///
/// See [`TypeRelation::Redundancy`] for more details.
@@ -2049,6 +2090,21 @@ impl<'db> Type<'db> {
return constraints.implies_subtype_of(db, self, target);
}
// Handle the new constraint-set-based assignability relation next. Comparisons with a
// typevar are translated directly into a constraint set.
if relation.is_constraint_set_assignability() {
// A typevar satisfies a relation when...it satisfies the relation. Yes that's a
// tautology! We're moving the caller's subtyping/assignability requirement into a
// constraint set. If the typevar has an upper bound or constraints, then the relation
// only has to hold when the typevar has a valid specialization (i.e., one that
// satisfies the upper bound/constraints).
if let Type::TypeVar(bound_typevar) = self {
return ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, target);
} else if let Type::TypeVar(bound_typevar) = target {
return ConstraintSet::constrain_typevar(db, bound_typevar, self, Type::object());
}
}
match (self, target) {
// Everything is a subtype of `object`.
(_, Type::NominalInstance(instance)) if instance.is_object() => {
@@ -2129,7 +2185,7 @@ impl<'db> Type<'db> {
);
ConstraintSet::from(match relation {
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
TypeRelation::Assignability => true,
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
TypeRelation::Redundancy => match target {
Type::Dynamic(_) => true,
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
@@ -2139,7 +2195,7 @@ impl<'db> Type<'db> {
}
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
TypeRelation::Assignability => true,
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
TypeRelation::Redundancy => match self {
Type::Dynamic(_) => true,
Type::Intersection(intersection) => {
@@ -2403,14 +2459,19 @@ impl<'db> Type<'db> {
TypeRelation::Subtyping
| TypeRelation::Redundancy
| TypeRelation::SubtypingAssuming(_) => self,
TypeRelation::Assignability => self.bottom_materialization(db),
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
self.bottom_materialization(db)
}
};
intersection.negative(db).iter().when_all(db, |&neg_ty| {
let neg_ty = match relation {
TypeRelation::Subtyping
| TypeRelation::Redundancy
| TypeRelation::SubtypingAssuming(_) => neg_ty,
TypeRelation::Assignability => neg_ty.bottom_materialization(db),
TypeRelation::Assignability
| TypeRelation::ConstraintSetAssignability => {
neg_ty.bottom_materialization(db)
}
};
self_ty.is_disjoint_from_impl(
db,
@@ -9777,6 +9838,22 @@ impl<'db> TypeVarInstance<'db> {
))
}
fn type_is_self_referential(self, db: &'db dyn Db, ty: Type<'db>) -> bool {
let identity = self.identity(db);
any_over_type(
db,
ty,
&|ty| match ty {
Type::TypeVar(bound_typevar) => identity == bound_typevar.typevar(db).identity(db),
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => {
identity == typevar.identity(db)
}
_ => false,
},
false,
)
}
#[salsa::tracked(
cycle_fn=lazy_bound_or_constraints_cycle_recover,
cycle_initial=lazy_bound_or_constraints_cycle_initial,
@@ -9799,6 +9876,11 @@ impl<'db> TypeVarInstance<'db> {
}
_ => return None,
};
if self.type_is_self_referential(db, ty) {
return None;
}
Some(TypeVarBoundOrConstraints::UpperBound(ty))
}
@@ -9846,6 +9928,15 @@ impl<'db> TypeVarInstance<'db> {
}
_ => return None,
};
if constraints
.elements(db)
.iter()
.any(|ty| self.type_is_self_referential(db, *ty))
{
return None;
}
Some(TypeVarBoundOrConstraints::Constraints(constraints))
}
@@ -9892,15 +9983,11 @@ impl<'db> TypeVarInstance<'db> {
let definition = self.definition(db)?;
let module = parsed_module(db, definition.file(db)).load(db);
match definition.kind(db) {
let ty = match definition.kind(db) {
// PEP 695 typevar
DefinitionKind::TypeVar(typevar) => {
let typevar_node = typevar.node(&module);
Some(definition_expression_type(
db,
definition,
typevar_node.default.as_ref()?,
))
definition_expression_type(db, definition, typevar_node.default.as_ref()?)
}
// legacy typevar / ParamSpec
DefinitionKind::Assignment(assignment) => {
@@ -9910,9 +9997,9 @@ impl<'db> TypeVarInstance<'db> {
let expr = &call_expr.arguments.find_keyword("default")?.value;
let default_type = definition_expression_type(db, definition, expr);
if known_class == Some(KnownClass::ParamSpec) {
Some(convert_type_to_paramspec_value(db, default_type))
convert_type_to_paramspec_value(db, default_type)
} else {
Some(default_type)
default_type
}
}
// PEP 695 ParamSpec
@@ -9920,10 +10007,16 @@ impl<'db> TypeVarInstance<'db> {
let paramspec_node = paramspec.node(&module);
let default_ty =
definition_expression_type(db, definition, paramspec_node.default.as_ref()?);
Some(convert_type_to_paramspec_value(db, default_ty))
convert_type_to_paramspec_value(db, default_ty)
}
_ => None,
_ => return None,
};
if self.type_is_self_referential(db, ty) {
return None;
}
Some(ty)
}
pub fn bind_pep695(self, db: &'db dyn Db) -> Option<BoundTypeVarInstance<'db>> {
@@ -12000,6 +12093,11 @@ pub(crate) enum TypeRelation<'db> {
/// are not actually subtypes of each other. (That is, `implies_subtype_of(false, int, str)`
/// will return true!)
SubtypingAssuming(ConstraintSet<'db>),
/// A placeholder for the new assignability relation that uses constraint sets to encode
/// relationships with a typevar. This will eventually replace `Assignability`, but allows us
/// to start using the new relation in a controlled manner in some places.
ConstraintSetAssignability,
}
impl TypeRelation<'_> {
@@ -12007,6 +12105,10 @@ impl TypeRelation<'_> {
matches!(self, TypeRelation::Assignability)
}
pub(crate) const fn is_constraint_set_assignability(self) -> bool {
matches!(self, TypeRelation::ConstraintSetAssignability)
}
pub(crate) const fn is_subtyping(self) -> bool {
matches!(self, TypeRelation::Subtyping)
}
@@ -12492,6 +12594,10 @@ impl<'db> CallableTypes<'db> {
}
}
fn as_slice(&self) -> &[CallableType<'db>] {
&self.0
}
fn into_inner(self) -> SmallVec<[CallableType<'db>; 1]> {
self.0
}

View File

@@ -637,7 +637,9 @@ impl<'db> ClassType<'db> {
| TypeRelation::SubtypingAssuming(_) => {
ConstraintSet::from(other.is_object(db))
}
TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)),
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
ConstraintSet::from(!other.is_final(db))
}
},
// Protocol, Generic, and TypedDict are not represented by a ClassType.

File diff suppressed because it is too large Load Diff

View File

@@ -13,15 +13,15 @@ use crate::types::class::ClassType;
use crate::types::class_base::ClassBase;
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::instance::{Protocol, ProtocolInstanceType};
use crate::types::signatures::{Parameters, ParametersKind};
use crate::types::signatures::Parameters;
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
use crate::types::variance::VarianceInferable;
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
use crate::types::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarIdentity, BoundTypeVarInstance,
CallableSignature, CallableType, CallableTypeKind, CallableTypes, ClassLiteral,
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Signature, Type,
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity,
ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor,
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity,
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
walk_type_var_bounds,
};
@@ -571,6 +571,14 @@ impl<'db> GenericContext<'db> {
let partial = PartialSpecialization {
generic_context: self,
types: &types,
// Don't recursively substitute type[i] in itself. Ideally, we could instead
// check if the result is self-referential after we're done applying the
// partial specialization. But when we apply a paramspec, we don't use the
// callable that it maps to directly; we create a new callable that reuses
// parts of it. That means we can't look for the previous type directly.
// Instead we use this to skip specializing the type in itself in the first
// place.
skip: Some(i),
};
let updated = types[i].apply_type_mapping(
db,
@@ -641,6 +649,7 @@ impl<'db> GenericContext<'db> {
let partial = PartialSpecialization {
generic_context: self,
types: &expanded[0..idx],
skip: None,
};
let default = default.apply_type_mapping(
db,
@@ -917,7 +926,11 @@ fn has_relation_in_invariant_position<'db>(
disjointness_visitor,
),
// And A <~ B (assignability) is Bottom[A] <: Top[B]
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
(
None,
Some(base_mat),
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability,
) => is_subtype_in_invariant_position(
db,
derived_type,
MaterializationKind::Bottom,
@@ -927,7 +940,11 @@ fn has_relation_in_invariant_position<'db>(
relation_visitor,
disjointness_visitor,
),
(Some(derived_mat), None, TypeRelation::Assignability) => is_subtype_in_invariant_position(
(
Some(derived_mat),
None,
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability,
) => is_subtype_in_invariant_position(
db,
derived_type,
derived_mat,
@@ -1438,6 +1455,9 @@ impl<'db> Specialization<'db> {
pub struct PartialSpecialization<'a, 'db> {
generic_context: GenericContext<'db>,
types: &'a [Type<'db>],
/// An optional typevar to _not_ substitute when applying the specialization. We use this to
/// avoid recursively substituting a type inside of itself.
skip: Option<usize>,
}
impl<'db> PartialSpecialization<'_, 'db> {
@@ -1452,6 +1472,9 @@ impl<'db> PartialSpecialization<'_, 'db> {
.generic_context
.variables_inner(db)
.get_index_of(&bound_typevar.identity(db))?;
if self.skip.is_some_and(|skip| skip == index) {
return Some(Type::Never);
}
self.types.get(index).copied()
}
}
@@ -1509,7 +1532,7 @@ impl<'db> SpecializationBuilder<'db> {
.map(|(identity, _)| self.types.get(identity).copied());
// TODO Infer the tuple spec for a tuple type
generic_context.specialize_partial(self.db, types)
generic_context.specialize_recursive(self.db, types)
}
fn add_type_mapping(
@@ -1543,6 +1566,59 @@ impl<'db> SpecializationBuilder<'db> {
}
}
/// Finds all of the valid specializations of a constraint set, and adds their type mappings to
/// the specialization that this builder is building up.
///
/// `formal` should be the top-level formal parameter type that we are inferring. This is used
/// by our literal promotion logic, which needs to know which typevars are affected by each
/// argument, and the variance of those typevars in the corresponding parameter.
///
/// TODO: This is a stopgap! Eventually, the builder will maintain a single constraint set for
/// the main specialization that we are building, and [`build`][Self::build] will build the
/// specialization directly from that constraint set. This method lets us migrate to that brave
/// new world incrementally, by using the new constraint set mechanism piecemeal for certain
/// type comparisons.
fn add_type_mappings_from_constraint_set(
&mut self,
formal: Type<'db>,
constraints: ConstraintSet<'db>,
mut f: impl FnMut(TypeVarAssignment<'db>) -> Option<Type<'db>>,
) {
let constraints = constraints.limit_to_valid_specializations(self.db);
constraints.for_each_path(self.db, |path| {
for (constraint, _) in path.positive_constraints() {
let typevar = constraint.typevar(self.db);
let lower = constraint.lower(self.db);
let upper = constraint.upper(self.db);
if !upper.is_object() {
let variance = formal.variance_of(self.db, typevar);
self.add_type_mapping(typevar, upper, variance, &mut f);
} else if !lower.is_never() {
let variance = formal.variance_of(self.db, typevar);
self.add_type_mapping(typevar, lower, variance, &mut f);
}
if let Type::TypeVar(lower_bound_typevar) = lower {
let variance = formal.variance_of(self.db, lower_bound_typevar);
self.add_type_mapping(
lower_bound_typevar,
Type::TypeVar(typevar),
variance,
&mut f,
);
}
if let Type::TypeVar(upper_bound_typevar) = upper {
let variance = formal.variance_of(self.db, upper_bound_typevar);
self.add_type_mapping(
upper_bound_typevar,
Type::TypeVar(typevar),
variance,
&mut f,
);
}
}
});
}
/// Infer type mappings for the specialization based on a given type and its declared type.
pub(crate) fn infer(
&mut self,
@@ -1572,6 +1648,15 @@ impl<'db> SpecializationBuilder<'db> {
polarity: TypeVarVariance,
mut f: &mut dyn FnMut(TypeVarAssignment<'db>) -> Option<Type<'db>>,
) -> Result<(), SpecializationError<'db>> {
// TODO: Eventually, the builder will maintain a constraint set, instead of a hash-map of
// type mappings, to represent the specialization that we are building up. At that point,
// this method will just need to compare `actual ≤ formal`, using constraint set
// assignability, and AND the result into the constraint set we are building.
//
// To make progress on that migration, we use constraint set assignability whenever
// possible when adding any new heuristics here. See the `Callable` clause below for an
// example.
if formal == actual {
return Ok(());
}
@@ -1827,43 +1912,19 @@ impl<'db> SpecializationBuilder<'db> {
}
(Type::Callable(formal_callable), _) => {
if let Some(actual_callable) = actual
.try_upcast_to_callable(self.db)
.and_then(CallableTypes::exactly_one)
{
// We're only interested in a formal callable of the form `Callable[P, ...]` for
// now where `P` is a `ParamSpec`.
// TODO: This would need to be updated once we support `Concatenate`
// TODO: What to do for overloaded callables?
let [signature] = formal_callable.signatures(self.db).as_slice() else {
return Ok(());
};
let formal_parameters = signature.parameters();
let ParametersKind::ParamSpec(typevar) = formal_parameters.kind() else {
return Ok(());
};
let paramspec_value = match actual_callable.signatures(self.db).as_slice() {
[] => return Ok(()),
[actual_signature] => match actual_signature.parameters().kind() {
ParametersKind::ParamSpec(typevar) => Type::TypeVar(typevar),
_ => Type::Callable(CallableType::new(
self.db,
CallableSignature::single(Signature::new(
actual_signature.parameters().clone(),
None,
)),
CallableTypeKind::ParamSpecValue,
)),
},
actual_signatures => Type::Callable(CallableType::new(
let Some(actual_callables) = actual.try_upcast_to_callable(self.db) else {
return Ok(());
};
for actual_callable in actual_callables.as_slice() {
let when = actual_callable
.signatures(self.db)
.when_constraint_set_assignable_to(
self.db,
CallableSignature::from_overloads(actual_signatures.iter().map(
|signature| Signature::new(signature.parameters().clone(), None),
)),
CallableTypeKind::ParamSpecValue,
)),
};
self.add_type_mapping(typevar, paramspec_value, polarity, &mut f);
formal_callable.signatures(self.db),
self.inferable,
);
self.add_type_mappings_from_constraint_set(formal, when, &mut f);
}
}

View File

@@ -17,11 +17,13 @@ use smallvec::{SmallVec, smallvec_inline};
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type, semantic_index};
use crate::semantic_index::definition::Definition;
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::constraints::{
ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension,
};
use crate::types::generics::{GenericContext, InferableTypeVars, walk_generic_context};
use crate::types::infer::{infer_deferred_types, infer_scope_types};
use crate::types::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind,
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableType, CallableTypeKind,
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, TypeContext,
TypeMapping, TypeRelation, VarianceInferable, todo_type,
@@ -90,10 +92,6 @@ impl<'db> CallableSignature<'db> {
self.overloads.iter()
}
pub(crate) fn as_slice(&self) -> &[Signature<'db>] {
&self.overloads
}
pub(crate) fn with_inherited_generic_context(
&self,
db: &'db dyn Db,
@@ -337,6 +335,38 @@ impl<'db> CallableSignature<'db> {
)
}
/// Checks whether the given slice contains a single signature, and that signature is a
/// `ParamSpec` signature. If so, returns the [`BoundTypeVarInstance`] for the `ParamSpec`,
/// along with the return type of the signature.
fn signatures_is_single_paramspec(
signatures: &[Signature<'db>],
) -> Option<(BoundTypeVarInstance<'db>, Option<Type<'db>>)> {
// TODO: This might need updating once we support `Concatenate`
let [signature] = signatures else {
return None;
};
signature
.parameters
.as_paramspec()
.map(|bound_typevar| (bound_typevar, signature.return_ty))
}
pub(crate) fn when_constraint_set_assignable_to(
&self,
db: &'db dyn Db,
other: &Self,
inferable: InferableTypeVars<'_, 'db>,
) -> ConstraintSet<'db> {
self.has_relation_to_impl(
db,
other,
inferable,
TypeRelation::ConstraintSetAssignability,
&HasRelationToVisitor::default(),
&IsDisjointVisitor::default(),
)
}
/// Implementation of subtyping and assignability between two, possible overloaded, callable
/// types.
fn has_relation_to_inner(
@@ -348,6 +378,116 @@ impl<'db> CallableSignature<'db> {
relation_visitor: &HasRelationToVisitor<'db>,
disjointness_visitor: &IsDisjointVisitor<'db>,
) -> ConstraintSet<'db> {
if relation.is_constraint_set_assignability() {
// TODO: Oof, maybe ParamSpec needs to live at CallableSignature, not Signature?
let self_is_single_paramspec = Self::signatures_is_single_paramspec(self_signatures);
let other_is_single_paramspec = Self::signatures_is_single_paramspec(other_signatures);
// If either callable is a ParamSpec, the constraint set should bind the ParamSpec to
// the other callable's signature. We also need to compare the return types — for
// instance, to verify in `Callable[P, int]` that the return type is assignable to
// `int`, or in `Callable[P, T]` to bind `T` to the return type of the other callable.
//
// TODO: This logic might need to move down into `Signature`, if we need paramspecs to
// be able to match a _subset_ of an overloaded callable. (In that case, we need to
// check each signature individually, and combine together the ones that match into the
// overloaded callable that the paramspec binds to.)
match (self_is_single_paramspec, other_is_single_paramspec) {
(
Some((self_bound_typevar, self_return_type)),
Some((other_bound_typevar, other_return_type)),
) => {
let param_spec_matches = ConstraintSet::constrain_typevar(
db,
self_bound_typevar,
Type::TypeVar(other_bound_typevar),
Type::TypeVar(other_bound_typevar),
);
let return_types_match = self_return_type.zip(other_return_type).when_some_and(
|(self_return_type, other_return_type)| {
self_return_type.has_relation_to_impl(
db,
other_return_type,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
},
);
return param_spec_matches.and(db, || return_types_match);
}
(Some((self_bound_typevar, self_return_type)), None) => {
let upper =
Type::Callable(CallableType::new(
db,
CallableSignature::from_overloads(other_signatures.iter().map(
|signature| Signature::new(signature.parameters().clone(), None),
)),
CallableTypeKind::ParamSpecValue,
));
let param_spec_matches = ConstraintSet::constrain_typevar(
db,
self_bound_typevar,
Type::Never,
upper,
);
let return_types_match = self_return_type.when_some_and(|self_return_type| {
other_signatures
.iter()
.filter_map(|signature| signature.return_ty)
.when_any(db, |other_return_type| {
self_return_type.has_relation_to_impl(
db,
other_return_type,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
});
return param_spec_matches.and(db, || return_types_match);
}
(None, Some((other_bound_typevar, other_return_type))) => {
let lower =
Type::Callable(CallableType::new(
db,
CallableSignature::from_overloads(self_signatures.iter().map(
|signature| Signature::new(signature.parameters().clone(), None),
)),
CallableTypeKind::ParamSpecValue,
));
let param_spec_matches = ConstraintSet::constrain_typevar(
db,
other_bound_typevar,
lower,
Type::object(),
);
let return_types_match = other_return_type.when_some_and(|other_return_type| {
self_signatures
.iter()
.filter_map(|signature| signature.return_ty)
.when_any(db, |self_return_type| {
self_return_type.has_relation_to_impl(
db,
other_return_type,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
});
return param_spec_matches.and(db, || return_types_match);
}
(None, None) => {}
}
}
match (self_signatures, other_signatures) {
([self_signature], [other_signature]) => {
// Base case: both callable types contain a single signature.
@@ -1133,7 +1273,13 @@ impl<'db> Signature<'db> {
// If either of the parameter lists is gradual (`...`), then it is assignable to and from
// any other parameter list, but not a subtype or supertype of any other parameter list.
if self.parameters.is_gradual() || other.parameters.is_gradual() {
return ConstraintSet::from(relation.is_assignability());
result.intersect(
db,
ConstraintSet::from(
relation.is_assignability() || relation.is_constraint_set_assignability(),
),
);
return result;
}
let mut parameters = ParametersZip {
@@ -1553,6 +1699,13 @@ impl<'db> Parameters<'db> {
matches!(self.kind, ParametersKind::Gradual)
}
pub(crate) const fn as_paramspec(&self) -> Option<BoundTypeVarInstance<'db>> {
match self.kind {
ParametersKind::ParamSpec(bound_typevar) => Some(bound_typevar),
_ => None,
}
}
/// Return todo parameters: (*args: Todo, **kwargs: Todo)
pub(crate) fn todo() -> Self {
Self {