Compare commits

..

116 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
Zanie Blue
8e13765b57 [ty] Use title for configuration code fences in ty reference documentation (#21992)
Part of https://github.com/astral-sh/ty/pull/1904
2025-12-15 16:36:08 -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
7d3b7c5754 [ty] Consistent ordering of constraint set specializations, take 2 (#21983)
In https://github.com/astral-sh/ruff/pull/21957, we tried to use
`union_or_intersection_elements_ordering` to provide a stable ordering
of the union and intersection elements that are created when determining
which type a typevar should specialize to. @AlexWaygood [pointed
out](https://github.com/astral-sh/ruff/pull/21551#discussion_r2616543762)
that this won't work, since that provides a consistent ordering within a
single process run, but does not provide a stable ordering across runs.

This is an attempt to produce a proper stable ordering for constraint
sets, so that we end up with consistent diagnostic and test output.

We do this by maintaining a new `source_order` field on each interior
BDD node, which records when that node's constraint was added to the
set. Several of the BDD operators (`and`, `or`, etc) now have
`_with_offset` variants, which update each `source_order` in the rhs to
be larger than any of the `source_order`s in the lhs. This is what
causes that field to be in line with (a) when you add each constraint to
the set, and (b) the order of the parameters you provide to `and`, `or`,
etc. Then we sort by that new field before constructing the
union/intersection types when creating a specialization.
2025-12-15 14:24:08 -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
25 changed files with 1604 additions and 611 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

@@ -166,8 +166,9 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
output.push('\n');
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
output.push('\n');
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
output.push_str("**Example usage**:\n\n");
output.push_str(&format_example(
"pyproject.toml",
&format_header(
field.scope,
field.example,
@@ -179,11 +180,11 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
output.push('\n');
}
fn format_example(header: &str, content: &str) -> String {
fn format_example(title: &str, header: &str, content: &str) -> String {
if header.is_empty() {
format!("```toml\n{content}\n```\n",)
format!("```toml title=\"{title}\"\n{content}\n```\n",)
} else {
format!("```toml\n{header}\n{content}\n```\n",)
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
}
}

View File

@@ -18,9 +18,9 @@ Valid severities are:
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.rules]
possibly-unresolved-reference = "warn"
division-by-zero = "ignore"
@@ -45,9 +45,9 @@ configuration setting.
**Type**: `list[str]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.environment]
extra-paths = ["./shared/my-search-path"]
```
@@ -76,9 +76,9 @@ This option can be used to point to virtual or system Python environments.
**Type**: `str`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.environment]
python = "./custom-venv-location/.venv"
```
@@ -103,9 +103,9 @@ If no platform is specified, ty will use the current platform:
**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | "all" | str`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.environment]
# Tailor type stubs and conditionalized type definitions to windows.
python-platform = "win32"
@@ -137,9 +137,9 @@ to reflect the differing contents of the standard library across Python versions
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.environment]
python-version = "3.12"
```
@@ -165,9 +165,9 @@ it will also be included in the first party search path.
**Type**: `list[str]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.environment]
# Multiple directories (priority order)
root = ["./src", "./lib", "./vendor"]
@@ -185,9 +185,9 @@ bundled as a zip file in the binary
**Type**: `str`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.environment]
typeshed = "/path/to/custom/typeshed"
```
@@ -240,9 +240,9 @@ If not specified, defaults to `[]` (excludes no files).
**Type**: `list[str]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[[tool.ty.overrides]]
exclude = [
"generated",
@@ -268,9 +268,9 @@ If not specified, defaults to `["**"]` (matches all files).
**Type**: `list[str]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[[tool.ty.overrides]]
include = [
"src",
@@ -292,9 +292,9 @@ severity levels or disable them entirely.
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[[tool.ty.overrides]]
include = ["src"]
@@ -358,9 +358,9 @@ to re-include `dist` use `exclude = ["!dist"]`
**Type**: `list[str]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.src]
exclude = [
"generated",
@@ -399,9 +399,9 @@ matches `<project_root>/src` and not `<project_root>/test/src`).
**Type**: `list[str]`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.src]
include = [
"src",
@@ -421,9 +421,9 @@ Enabled by default.
**Type**: `bool`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.src]
respect-ignore-files = false
```
@@ -450,9 +450,9 @@ it will also be included in the first party search path.
**Type**: `str`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.src]
root = "./app"
```
@@ -471,9 +471,9 @@ Defaults to `false`.
**Type**: `bool`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.terminal]
# Error if ty emits any warning-level diagnostics.
error-on-warning = true
@@ -491,9 +491,9 @@ Defaults to `full`.
**Type**: `full | concise`
**Example usage** (`pyproject.toml`):
**Example usage**:
```toml
```toml title="pyproject.toml"
[tool.ty.terminal]
output-format = "concise"
```

View File

@@ -1,12 +1,12 @@
use crate::docstring::Docstring;
use crate::goto::{GotoTarget, find_goto_target};
use crate::{Db, HasNavigationTargets, MarkupKind, NavigationTarget, RangedValue};
use crate::{Db, MarkupKind, RangedValue};
use ruff_db::files::{File, FileRange};
use ruff_db::parsed::parsed_module;
use ruff_text_size::{Ranged, TextSize};
use std::fmt;
use std::fmt::Formatter;
use ty_python_semantic::types::{KnownInstanceType, Type, TypeDetail, TypeVarVariance};
use ty_python_semantic::types::{KnownInstanceType, Type, TypeVarVariance};
use ty_python_semantic::{DisplaySettings, SemanticModel};
pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> {
@@ -59,21 +59,13 @@ pub struct Hover<'db> {
contents: Vec<HoverContent<'db>>,
}
type Linkify<'a> = &'a dyn Fn(&NavigationTarget) -> Option<String>;
impl<'db> Hover<'db> {
/// Renders the hover to a string using the specified markup kind.
pub const fn display<'a>(
&'a self,
db: &'db dyn Db,
kind: MarkupKind,
linkify: Linkify<'a>,
) -> DisplayHover<'db, 'a> {
pub const fn display<'a>(&'a self, db: &'db dyn Db, kind: MarkupKind) -> DisplayHover<'db, 'a> {
DisplayHover {
db,
hover: self,
kind,
linkify,
}
}
@@ -104,7 +96,6 @@ pub struct DisplayHover<'db, 'a> {
db: &'db dyn Db,
hover: &'a Hover<'db>,
kind: MarkupKind,
linkify: Linkify<'a>,
}
impl fmt::Display for DisplayHover<'_, '_> {
@@ -115,7 +106,7 @@ impl fmt::Display for DisplayHover<'_, '_> {
self.kind.horizontal_line().fmt(f)?;
}
content.display(self.db, self.kind, self.linkify).fmt(f)?;
content.display(self.db, self.kind).fmt(f)?;
first = false;
}
@@ -131,17 +122,11 @@ pub enum HoverContent<'db> {
}
impl<'db> HoverContent<'db> {
fn display<'a>(
&'a self,
db: &'db dyn Db,
kind: MarkupKind,
linkify: Linkify<'a>,
) -> DisplayHoverContent<'a, 'db> {
fn display(&self, db: &'db dyn Db, kind: MarkupKind) -> DisplayHoverContent<'_, 'db> {
DisplayHoverContent {
db,
content: self,
kind,
linkify,
}
}
}
@@ -150,7 +135,6 @@ pub(crate) struct DisplayHoverContent<'a, 'db> {
db: &'db dyn Db,
content: &'a HoverContent<'db>,
kind: MarkupKind,
linkify: Linkify<'a>,
}
impl fmt::Display for DisplayHoverContent<'_, '_> {
@@ -167,84 +151,21 @@ impl fmt::Display for DisplayHoverContent<'_, '_> {
Some(TypeVarVariance::Bivariant) => " (bivariant)",
None => "",
};
if self.kind == MarkupKind::Markdown {
let details = ty.display(self.db).to_string_parts();
// Ok so the idea here is that we potentially have a random soup of spans here,
// and each byte of the string can have at most one target associate with it.
// Thankfully, they were generally pushed in print order, with the inner smaller types
// appearing before the outer bigger ones.
//
// So we record where we are in the string, and every time we find a type, we
// check if it's further along in the string. If it is, great, we give it the
// span for its range, and then advance where we are.
let mut offset = 0;
for (target, detail) in details.targets.iter().zip(&details.details) {
match detail {
TypeDetail::Type(ty) => {
let start = target.start().to_usize();
let end = target.end().to_usize();
// If we skipped over some bytes, push them with no target
if start > offset {
write!(f, "{}", escape(&details.label[offset..start]))?;
}
// Ok, this is the first type that claimed these bytes, give it the target
if start >= offset {
if let Some(target) =
ty.navigation_targets(self.db).into_iter().next()
&& let Some(uri) = (self.linkify)(&target)
{
write!(
f,
"[{}]({})",
escape(&details.label[start..end]),
uri
)?;
} else {
write!(f, "{}", escape(&details.label[start..end]))?;
}
offset = end;
}
}
TypeDetail::SignatureStart
| TypeDetail::SignatureEnd
| TypeDetail::Parameter(_) => {
// Don't care about these
}
}
}
// "flush" the rest of the label without any target
if offset < details.label.len() {
write!(f, "{}", escape(&details.label[offset..details.label.len()]))?;
}
write!(f, "{}", escape(variance))
} else {
self.kind
.fenced_code_block(
format!(
"{}{variance}",
ty.display_with(self.db, DisplaySettings::default().multiline())
),
"python",
)
.fmt(f)
}
self.kind
.fenced_code_block(
format!(
"{}{variance}",
ty.display_with(self.db, DisplaySettings::default().multiline())
),
"python",
)
.fmt(f)
}
HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f),
}
}
}
fn escape(x: &str) -> String {
x.replace('[', "\\[")
.replace(']', "\\]")
.replace('(', "\\(")
.replace(')', "\\)")
.replace('`', "\\`")
.replace('#', "\\#")
}
#[cfg(test)]
mod tests {
use crate::tests::{CursorTest, cursor_test};
@@ -3749,9 +3670,9 @@ def function():
write!(
&mut buf,
"{plaintext}{line}{markdown}{line}",
plaintext = hover.display(&self.db, MarkupKind::PlainText, &|_| None),
plaintext = hover.display(&self.db, MarkupKind::PlainText),
line = MarkupKind::PlainText.horizontal_line(),
markdown = hover.display(&self.db, MarkupKind::Markdown, &|_| None),
markdown = hover.display(&self.db, MarkupKind::Markdown),
)
.unwrap();

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

@@ -689,20 +689,10 @@ impl<'db> IntersectionBuilder<'db> {
}
}
pub(crate) fn order_elements(mut self, val: bool) -> Self {
self.order_elements = val;
self
}
pub(crate) fn add_positive(self, ty: Type<'db>) -> Self {
self.add_positive_impl(ty, &mut vec![])
}
pub(crate) fn add_positive_in_place(&mut self, ty: Type<'db>) {
let updated = std::mem::replace(self, Self::empty(self.db)).add_positive(ty);
*self = updated;
}
pub(crate) fn add_positive_impl(
mut self,
ty: Type<'db>,

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 {

View File

@@ -1,6 +1,6 @@
use std::borrow::Cow;
use crate::document::{FileRangeExt, PositionExt, ToRangeExt};
use crate::document::{FileRangeExt, PositionExt};
use crate::server::api::traits::{
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
};
@@ -8,7 +8,7 @@ use crate::session::DocumentSnapshot;
use crate::session::client::Client;
use lsp_types::request::HoverRequest;
use lsp_types::{HoverContents, HoverParams, MarkupContent, Url};
use ty_ide::{MarkupKind, NavigationTarget, hover};
use ty_ide::{MarkupKind, hover};
use ty_project::ProjectDatabase;
pub(crate) struct HoverRequestHandler;
@@ -61,23 +61,7 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler {
(MarkupKind::PlainText, lsp_types::MarkupKind::PlainText)
};
let to_lsp_link = |target: &NavigationTarget| -> Option<String> {
let file = target.file();
let location = target
.focus_range()
.to_lsp_range(db, file, snapshot.encoding())?
.to_location()?;
let uri = location.uri;
let line1 = location.range.start.line;
let char1 = location.range.start.character;
let line2 = location.range.end.line;
let char2 = location.range.end.character;
Some(format!("{uri}#L{line1}C{char1}-L{line2}C{char2}"))
};
let contents = range_info
.display(db, markup_kind, &to_lsp_link)
.to_string();
let contents = range_info.display(db, markup_kind).to_string();
Ok(Some(lsp_types::Hover {
contents: HoverContents::Markup(MarkupContent {

View File

@@ -399,7 +399,7 @@ impl Workspace {
Ok(Some(Hover {
markdown: range_info
.display(&self.db, MarkupKind::Markdown, &|_| None)
.display(&self.db, MarkupKind::Markdown)
.to_string(),
range: source_range,
}))