Compare commits

..

40 Commits

Author SHA1 Message Date
Douglas Creager
eb95e49f96 clean up some claude-isms 2025-10-09 10:06:42 -04:00
Douglas Creager
0817a367e1 spelling 2025-10-09 09:57:53 -04:00
Douglas Creager
34b463d0a1 reword some docs 2025-10-09 09:55:51 -04:00
Douglas Creager
b438631de2 clean up the diff 2025-10-09 08:22:07 -04:00
Douglas Creager
fbc211f344 update tests 2025-10-09 08:20:12 -04:00
Douglas Creager
1810b7c7a3 precommit 2025-10-09 08:09:50 -04:00
Douglas Creager
39353da108 remove finished plan 2025-10-09 08:09:07 -04:00
Douglas Creager
7b88440fc2 update tests 2025-10-09 08:08:57 -04:00
Douglas Creager
8426cc6915 use ty_ide 2025-10-08 16:09:42 -04:00
Douglas Creager
c08a2d6d65 [ty] Fix hover to prefer expression nodes over identifiers
When hovering on an attribute name like 'value' in 'instance.value',
the minimal covering node is the Identifier, but we need the Attribute
expression to get the type. Update find_covering_node to track both
the minimal node and minimal expression, preferring the expression.

This fixes hover on attribute accesses. All hover tests now pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 15:54:27 -04:00
Douglas Creager
6ddf729864 clean up mdtest 2025-10-08 15:51:12 -04:00
Douglas Creager
eaba8bc61e [ty] Add comprehensive hover.md mdtest (partial)
Add hover.md with examples of hover assertions across different
expression types. Some tests still need arrow alignment fixes, but
many sections are passing including basic literals, function definitions,
comprehensions, and the simple hover test file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 15:25:10 -04:00
Douglas Creager
b18d213869 [ty] Update PLAN.md with testing progress 2025-10-08 15:08:59 -04:00
Douglas Creager
847e5f0c68 [ty] Handle expression statements in hover type inference
When hovering, if we find a statement node (like StmtExpr), extract the
expression from within it. This allows hover assertions to work on
standalone expressions like variable references.

Also add a simple working mdtest to demonstrate hover assertions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 15:08:23 -04:00
Douglas Creager
41772466d5 [ty_test] Store CheckOutput references in SortedCheckOutputs
Update SortedCheckOutputs to store references to CheckOutput instead
of owned values, matching the design of the previous SortedDiagnostics
implementation. This avoids unnecessary cloning and makes the API more
consistent.

Changes:
- SortedCheckOutputs now stores Vec<&CheckOutput>
- new() takes IntoIterator<Item = &CheckOutput>
- LineCheckOutputs.outputs is now &[&CheckOutput]
- Implement Unmatched and UnmatchedWithColumn for &CheckOutput
- Update match_line to take &[&CheckOutput]

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:57:33 -04:00
Douglas Creager
180d9de472 [ty_test] Fix clippy warnings in hover module
- Elide unnecessary explicit lifetimes in find_covering_node
- Add backticks around CheckOutputs in doc comment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:48:55 -04:00
Douglas Creager
adf58b6c19 [ty_test] Change match_line to take slice of CheckOutput
Update match_line signature to accept &[CheckOutput] instead of
&LineCheckOutputs for consistency with the previous API that took
&[Diagnostic].

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:47:45 -04:00
Douglas Creager
b683da8cde [ty_test] Store hover column as OneIndexed
Store the hover assertion column as OneIndexed since that's the type
returned by line_column() and required by SourceLocation. This
eliminates unnecessary conversions between zero-based and one-based
indexing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:41:34 -04:00
Douglas Creager
6d1c549c4e [ty_test] Simplify column calculation using line_column
Instead of manually calculating character offsets, use line_column()
which does all the work for us:

1. Find the byte offset of the arrow in the comment text
2. Add that to the comment's TextRange to get the arrow's absolute position
3. Call line_column() on that position to get the character column

This is much simpler and lets line_column handle all the UTF-8/UTF-32
conversion complexity.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:36:24 -04:00
Douglas Creager
c574dff6b0 [ty_test] Fix column units: use character offset not byte offset
The column field was being treated inconsistently - calculated as a
character offset but used as a byte offset. This would break on any
line with multi-byte UTF-8 characters before the hover position.

Fixes:
1. Use chars().position() instead of find() to get character offset
2. Use LineIndex::offset() with PositionEncoding::Utf32 to properly
   convert character offset to byte offset (TextSize)
3. Document that column is a UTF-32 character offset

This ensures hover assertions work correctly with Unicode text.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:33:29 -04:00
Douglas Creager
8c12fcb927 [ty_test] Use named fields for UnparsedAssertion::Hover
Convert the Hover variant from tuple-style to named fields for better
clarity and self-documentation.

Before: Hover(&'a str, &'a str, TextRange)
After: Hover { expected_type, full_comment, range }

This makes the code more readable and easier to maintain.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:26:28 -04:00
Douglas Creager
a2eaf7ce26 [ty_test] Calculate hover column at parse time
Move the column calculation from generate_hover_outputs into
HoverAssertion::from_str() by passing LineIndex and SourceText to
the parse() method.

This simplifies the code and centralizes the column calculation logic
in one place during parsing, rather than spreading it across multiple
locations. The HoverAssertion now directly stores the final column
position in the line, ready to use.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:23:07 -04:00
Douglas Creager
c3626a6d74 [ty_test] Fix hover column calculation to use line position
The previous implementation calculated the down arrow position within
the comment text, but then incorrectly used that as an offset into the
target line. This only worked if the comment started at column 0.

Now we properly calculate the column position by:
1. Finding the arrow offset within the comment text
2. Getting the comment's column position in its line
3. Adding these together to get the arrow's column in the line
4. Using that column to index into the target line

This fixes hover assertions when comments are indented or don't start
at the beginning of the line.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:21:03 -04:00
Douglas Creager
8221450cbc [ty_test] Store HoverAssertion.column as zero-based
Change the column field in HoverAssertion from OneIndexed to usize,
storing it as a zero-based index. This matches how it's used and
eliminates unnecessary conversions between zero-based and one-based
indexing.

The arrow position from find() is already zero-based, and TextSize
uses zero-based offsets, so this is more natural.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:18:03 -04:00
Douglas Creager
93db8833ef [ty_test] Add use statements to hover.rs
Import InlineFileAssertions, ParsedAssertion, and UnparsedAssertion
at the module level instead of using fully qualified crate::assertion
names throughout the code.

This makes the code cleaner and more idiomatic.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:16:16 -04:00
Douglas Creager
51d5bc709d [ty_test] Calculate hover column at parse time
Move the column calculation from generate_hover_outputs to the
HoverAssertion parsing logic. This makes better use of the existing
column field in HoverAssertion.

Changes:
- UnparsedAssertion::Hover now stores both the expected type and the
  full comment text
- HoverAssertion::from_str() now takes both parameters and calculates
  the column from the down arrow position in the full comment
- generate_hover_outputs() now reads the column from the parsed
  assertion instead of recalculating it

This eliminates redundant calculations and makes the column field
actually useful.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 14:09:00 -04:00
Douglas Creager
0198857224 [ty_test] Simplify unmatched output handling
Implement UnmatchedWithColumn for CheckOutput and update the column()
method to work with CheckOutput instead of just Diagnostic.

This allows eliminating the match statement when formatting unmatched
outputs - we can now just call unmatched_with_column() on all outputs
uniformly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 13:57:58 -04:00
Douglas Creager
35b568dd6b [ty_test] Move HoverOutput to hover module
Move the HoverOutput type from check_output.rs to hover.rs where it
logically belongs. The check_output module should only contain the
CheckOutput enum and sorting infrastructure, while hover-specific
types belong in the hover module.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 13:54:59 -04:00
Douglas Creager
35a5fd767d [ty_test] Extract HoverOutput type from CheckOutput enum
Create a dedicated HoverOutput struct to hold hover result data,
replacing the inline fields in CheckOutput::Hover variant.

This allows implementing Unmatched and UnmatchedWithColumn traits
directly on HoverOutput, simplifying the CheckOutput implementations
to simple delegation.

Benefits:
- Better separation of concerns
- Cleaner trait implementations
- More consistent with Diagnostic handling
- Easier to extend HoverOutput in the future

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 13:52:58 -04:00
Douglas Creager
ac1b68c56d [ty_test] Refactor: rename diagnostic.rs to check_output.rs
SortedDiagnostics is now replaced by SortedCheckOutputs, which handles
both diagnostics and hover results. This refactoring:

- Renames diagnostic.rs to check_output.rs to better reflect its purpose
- Moves CheckOutput and SortedCheckOutputs definitions from matcher.rs
  to check_output.rs where they belong
- Removes the now-unused SortedDiagnostics infrastructure
- Ports the test to use SortedCheckOutputs instead of SortedDiagnostics
- Updates all imports throughout the codebase

The check_output module now serves as the central location for sorting
and grouping all types of check outputs (diagnostics and hover results)
by line number.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 13:20:18 -04:00
Douglas Creager
ab261360e4 [ty_test] Use let-else pattern in generate_hover_outputs
Replace nested if-let blocks with let-else + continue to reduce
indentation and improve readability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 13:09:29 -04:00
Douglas Creager
19600ecd51 [ty_test] Fix hover assertion line number calculation
The previous implementation incorrectly assumed the target line was
always line_number + 1, which breaks when multiple assertion comments
are stacked on consecutive lines.

Now generate_hover_outputs accepts the parsed InlineFileAssertions,
which already correctly associates each assertion with its target line
number (accounting for stacked comments).

For the column position, we extract it directly from the down arrow
position in the UnparsedAssertion::Hover text, avoiding the need to
parse the assertion ourselves (parsing happens later in the matcher).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 13:05:07 -04:00
Douglas Creager
319f5be78c [ty_test] Simplify find_covering_node by comparing range lengths
Replace the leave_node() approach with a simpler strategy: just compare
the range lengths and keep the smallest node that covers the offset.

This is more direct and easier to understand than tracking when we
leave nodes. The minimal covering node is simply the one with the
shortest range that contains the offset.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:54:42 -04:00
Douglas Creager
6370aea644 [ty_test] Fix find_covering_node to correctly find minimal node
The previous implementation had a bug: it would overwrite `found` for
every matching node in source order, which could incorrectly select a
sibling node instead of the minimal covering node.

Now use the same approach as ty_ide's covering_node:
- Use leave_node() to detect when we've finished traversing a subtree
- Set a `found` flag when leaving the minimal node to prevent further
  updates
- This ensures we return the deepest (most specific) node that covers
  the offset, not just the last one visited in source order

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:53:06 -04:00
Douglas Creager
9d8e35b165 [ty_test] Simplify infer_type_at_position using as_expr_ref()
Replace the large match statement over all AnyNodeRef expression variants
with a simple call to as_expr_ref(). This helper method handles all
expression-related variants automatically, reducing the function from
~60 lines to just 6 lines.

Also removes statement-related variants (StmtFunctionDef, StmtClassDef,
StmtExpr) to focus only on expression nodes, which is the primary use
case for hover assertions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:50:28 -04:00
Douglas Creager
5c75e91abe [ty_test] Refactor hover logic into separate module
Move hover-related functions from lib.rs into a new hover.rs module
to improve code organization:
- find_covering_node() - locate AST nodes at specific offsets
- infer_type_at_position() - get inferred types using SemanticModel
- generate_hover_outputs() - scan for hover assertions and generate results

This keeps lib.rs focused on the main test execution flow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:47:15 -04:00
Douglas Creager
6e73b859ef Update PLAN.md - mark steps 4 & 5 complete
All core hover assertion functionality is now implemented and compiling!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:41:25 -04:00
Douglas Creager
11e5ecb91e Implement hover type inference and matching for mdtest
- Add find_covering_node() to locate AST nodes at specific positions
- Add infer_type_at_position() to get type information using ty_python_semantic
- Add generate_hover_outputs() to scan for hover assertions and generate results
- Integrate hover outputs into check flow in run_test()
- Implement hover matching logic in matcher.rs to compare types
- Avoid adding ty_ide dependency; instead use ty_python_semantic directly

The implementation extracts hover assertions from comments, computes the target
position from the down arrow location, infers the type at that position using
the AST and SemanticModel, and matches it against the expected type in the
assertion.

Hover assertions now work end-to-end in mdtest files!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:40:52 -04:00
Douglas Creager
be47fbe0f6 Add CheckOutput enum to support hover results in mdtest
- Create CheckOutput enum with Diagnostic and Hover variants
- Replace SortedDiagnostics with SortedCheckOutputs in matcher
- Update match_file to accept &[CheckOutput] instead of &[Diagnostic]
- Update matching logic to handle both diagnostics and hover results
- Implement Unmatched trait for CheckOutput
- Convert diagnostics to CheckOutput in lib.rs before matching

This infrastructure allows hover assertions to be matched against hover
results without polluting the DiagnosticId enum with test-specific IDs.
The hover matching logic will be implemented in the next commit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:28:09 -04:00
Douglas Creager
37effea8fd Add hover assertion type to mdtest framework
- Add Hover variant to UnparsedAssertion and ParsedAssertion enums
- Create HoverAssertion struct with column and expected_type fields
- Add HoverAssertionParseError enum for validation errors
- Update from_comment() to recognize '# hover:' and '# ↓ hover:' patterns
- Simplified design: down arrow must appear immediately before 'hover' keyword
- Add placeholder matching logic in matcher.rs (to be completed)
- Add PLAN.md to track implementation progress

The hover assertion syntax uses a down arrow to indicate column position:
    # ↓ hover: expected_type
    expression_to_hover

This will enable testing hover functionality in mdtest files, similar to
how ty_ide tests work with <CURSOR> markers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 12:19:21 -04:00
138 changed files with 4414 additions and 6946 deletions

View File

@@ -452,7 +452,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
- name: "Install cargo-fuzz"
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
@@ -703,7 +703,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
- uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -953,7 +953,7 @@ jobs:
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
- name: "Run benchmarks"
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
with:
mode: instrumentation
run: cargo codspeed run
@@ -988,23 +988,19 @@ jobs:
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
with:
mode: instrumentation
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
benchmarks-walltime:
name: "benchmarks walltime (${{ matrix.benchmarks }})"
runs-on: codspeed-macro
needs: determine_changes
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
strategy:
matrix:
benchmarks:
- "medium|multithreaded"
- "small|large"
env:
TY_LOG: ruff_benchmark=debug
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -1026,7 +1022,7 @@ jobs:
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
env:
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
# appear to provide much useful insight for our walltime benchmarks right now
@@ -1034,5 +1030,5 @@ jobs:
CODSPEED_PERF_ENABLED: false
with:
mode: walltime
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}

148
Cargo.lock generated
View File

@@ -50,9 +50,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.21"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -65,9 +65,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.13"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-lossy"
@@ -214,6 +214,15 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bincode"
version = "2.0.1"
@@ -234,26 +243,6 @@ dependencies = [
"virtue",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -327,9 +316,9 @@ dependencies = [
[[package]]
name = "camino"
version = "1.2.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
dependencies = [
"serde_core",
]
@@ -361,15 +350,6 @@ dependencies = [
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
@@ -420,17 +400,6 @@ dependencies = [
"half",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "4.5.48"
@@ -517,17 +486,16 @@ dependencies = [
[[package]]
name = "codspeed"
version = "4.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f62ea8934802f8b374bf691eea524c3aa444d7014f604dd4182a3667b69510"
checksum = "35584c5fcba8059780748866387fb97c5a203bcfc563fc3d0790af406727a117"
dependencies = [
"anyhow",
"bindgen",
"cc",
"bincode 1.3.3",
"colored 2.2.0",
"glob",
"libc",
"nix 0.30.1",
"nix 0.29.0",
"serde",
"serde_json",
"statrs",
@@ -536,22 +504,20 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "4.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87efbc015fc0ff1b2001cd87df01c442824de677e01a77230bf091534687abb"
checksum = "78f6c1c6bed5fd84d319e8b0889da051daa361c79b7709c9394dfe1a882bba67"
dependencies = [
"clap",
"codspeed",
"codspeed-criterion-compat-walltime",
"colored 2.2.0",
"regex",
]
[[package]]
name = "codspeed-criterion-compat-walltime"
version = "4.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae5713ace440123bb4f1f78dd068d46872cb8548bfe61f752e7b2ad2c06d7f00"
checksum = "c989289ce6b1cbde72ed560496cb8fbf5aa14d5ef5666f168e7f87751038352e"
dependencies = [
"anes",
"cast",
@@ -574,22 +540,20 @@ dependencies = [
[[package]]
name = "codspeed-divan-compat"
version = "4.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b4214b974f8f5206497153e89db90274e623f06b00bf4b9143eeb7735d975d"
checksum = "adf64eda57508448d59efd940bad62ede7c50b0d451a150b8d6a0eca642792a6"
dependencies = [
"clap",
"codspeed",
"codspeed-divan-compat-macros",
"codspeed-divan-compat-walltime",
"regex",
]
[[package]]
name = "codspeed-divan-compat-macros"
version = "4.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a53f34a16cb70ce4fd9ad57e1db016f0718e434f34179ca652006443b9a39967"
checksum = "058167258e819b16a4ba601fdfe270349ef191154758dbce122c62a698f70ba8"
dependencies = [
"divan-macros",
"itertools 0.14.0",
@@ -601,9 +565,9 @@ dependencies = [
[[package]]
name = "codspeed-divan-compat-walltime"
version = "4.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a5099050c8948dce488b8eaa2e68dc5cf571cb8f9fce99aaaecbdddb940bcd"
checksum = "48f9866ee3a4ef9d2868823ea5811886763af244f2df584ca247f49281c43f1f"
dependencies = [
"cfg-if",
"clap",
@@ -1563,7 +1527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
"hashbrown 0.16.0",
"serde",
"serde_core",
]
@@ -1837,15 +1801,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.177"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libcst"
version = "1.8.5"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d56bcd52d9b5e5f43e7fba20eb1f423ccb18c84cdf1cb506b8c1b95776b0b49"
checksum = "052ef5d9fc958a51aeebdf3713573b36c6fd6eed0bf0e60e204d2c0f8cf19b9f"
dependencies = [
"annotate-snippets",
"libcst_derive",
@@ -1858,24 +1822,14 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "1.8.5"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fcf5a725c4db703660124fe0edb98285f1605d0b87b7ee8684b699764a4f01a"
checksum = "a91a751afee92cbdd59d4bc6754c7672712eec2d30a308f23de4e3287b2929cb"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link 0.2.0",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.44"
@@ -2017,9 +1971,9 @@ checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9"
[[package]]
name = "memchr"
version = "2.7.6"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "memoffset"
@@ -2528,16 +2482,6 @@ dependencies = [
"yansi",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
@@ -2569,9 +2513,9 @@ dependencies = [
[[package]]
name = "pyproject-toml"
version = "0.13.7"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d755483ad14b49e76713b52285235461a5b4f73f17612353e11a5de36a5fd2"
checksum = "ec768e063102b426e8962989758115e8659485124de9207bc365fab524125d65"
dependencies = [
"indexmap",
"pep440_rs",
@@ -2769,9 +2713,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.3"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
@@ -2781,9 +2725,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.11"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
@@ -2820,7 +2764,7 @@ dependencies = [
"anyhow",
"argfile",
"assert_fs",
"bincode",
"bincode 2.0.1",
"bitflags 2.9.4",
"cachedir",
"clap",
@@ -4499,7 +4443,6 @@ dependencies = [
"colored 3.0.0",
"insta",
"memchr",
"path-slash",
"regex",
"ruff_db",
"ruff_index",
@@ -4517,6 +4460,7 @@ dependencies = [
"thiserror 2.0.16",
"toml",
"tracing",
"ty_ide",
"ty_python_semantic",
"ty_static",
"ty_vendored",

View File

@@ -71,8 +71,8 @@ clap = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.6.0" }
clearscreen = { version = "4.0.0" }
csv = { version = "1.3.1" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
colored = { version = "3.0.0" }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }

View File

@@ -599,7 +599,7 @@ impl<'a> ProjectBenchmark<'a> {
self.project
.check_paths()
.iter()
.map(|path| SystemPathBuf::from(*path))
.map(|path| path.to_path_buf())
.collect(),
);
@@ -645,8 +645,8 @@ fn hydra(criterion: &mut Criterion) {
name: "hydra-zen",
repository: "https://github.com/mit-ll-responsible-ai/hydra-zen",
commit: "dd2b50a9614c6f8c46c5866f283c8f7e7a960aa8",
paths: &["src"],
dependencies: &["pydantic", "beartype", "hydra-core"],
paths: vec![SystemPath::new("src")],
dependencies: vec!["pydantic", "beartype", "hydra-core"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
@@ -662,8 +662,8 @@ fn attrs(criterion: &mut Criterion) {
name: "attrs",
repository: "https://github.com/python-attrs/attrs",
commit: "a6ae894aad9bc09edc7cdad8c416898784ceec9b",
paths: &["src"],
dependencies: &[],
paths: vec![SystemPath::new("src")],
dependencies: vec![],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
@@ -679,8 +679,8 @@ fn anyio(criterion: &mut Criterion) {
name: "anyio",
repository: "https://github.com/agronholm/anyio",
commit: "561d81270a12f7c6bbafb5bc5fad99a2a13f96be",
paths: &["src"],
dependencies: &[],
paths: vec![SystemPath::new("src")],
dependencies: vec![],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
@@ -696,8 +696,8 @@ fn datetype(criterion: &mut Criterion) {
name: "DateType",
repository: "https://github.com/glyph/DateType",
commit: "57c9c93cf2468069f72945fc04bf27b64100dad8",
paths: &["src"],
dependencies: &[],
paths: vec![SystemPath::new("src")],
dependencies: vec![],
max_dep_date: "2025-07-04",
python_version: PythonVersion::PY313,
},

View File

@@ -1,6 +1,7 @@
use divan::{Bencher, bench};
use std::fmt::{Display, Formatter};
use divan::{Bencher, bench};
use rayon::ThreadPoolBuilder;
use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject};
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
@@ -12,39 +13,29 @@ use ty_project::metadata::value::{RangedValue, RelativePathBuf};
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
struct Benchmark<'a> {
project: RealWorldProject<'a>,
installed_project: std::sync::OnceLock<InstalledProject<'a>>,
project: InstalledProject<'a>,
max_diagnostics: usize,
}
impl<'a> Benchmark<'a> {
const fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
let setup_project = project.setup().expect("Failed to setup project");
Self {
project,
installed_project: std::sync::OnceLock::new(),
project: setup_project,
max_diagnostics,
}
}
fn installed_project(&self) -> &InstalledProject<'a> {
self.installed_project.get_or_init(|| {
self.project
.clone()
.setup()
.expect("Failed to setup project")
})
}
fn setup_iteration(&self) -> ProjectDatabase {
let installed_project = self.installed_project();
let root = SystemPathBuf::from_path_buf(installed_project.path.clone()).unwrap();
let root = SystemPathBuf::from_path_buf(self.project.path.clone()).unwrap();
let system = OsSystem::new(&root);
let mut metadata = ProjectMetadata::discover(&root, &system).unwrap();
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(installed_project.config.python_version)),
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
@@ -55,7 +46,7 @@ impl<'a> Benchmark<'a> {
db.project().set_included_paths(
&mut db,
installed_project
self.project
.check_paths()
.iter()
.map(|path| SystemPath::absolute(path, &root))
@@ -67,7 +58,7 @@ impl<'a> Benchmark<'a> {
impl Display for Benchmark<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.project.name.fmt(f)
f.write_str(self.project.config.name)
}
}
@@ -84,150 +75,166 @@ fn check_project(db: &ProjectDatabase, max_diagnostics: usize) {
);
}
static ALTAIR: Benchmark = Benchmark::new(
RealWorldProject {
name: "altair",
repository: "https://github.com/vega/altair",
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
paths: &["altair"],
dependencies: &[
"jinja2",
"narwhals",
"numpy",
"packaging",
"pandas-stubs",
"pyarrow-stubs",
"pytest",
"scipy-stubs",
"types-jsonschema",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
1000,
);
static ALTAIR: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "altair",
repository: "https://github.com/vega/altair",
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
paths: vec![SystemPath::new("altair")],
dependencies: vec![
"jinja2",
"narwhals",
"numpy",
"packaging",
"pandas-stubs",
"pyarrow-stubs",
"pytest",
"scipy-stubs",
"types-jsonschema",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
1000,
)
});
static COLOUR_SCIENCE: Benchmark = Benchmark::new(
RealWorldProject {
name: "colour-science",
repository: "https://github.com/colour-science/colour",
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
paths: &["colour"],
dependencies: &[
"matplotlib",
"numpy",
"pandas-stubs",
"pytest",
"scipy-stubs",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310,
},
600,
);
static COLOUR_SCIENCE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "colour-science",
repository: "https://github.com/colour-science/colour",
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
paths: vec![SystemPath::new("colour")],
dependencies: vec![
"matplotlib",
"numpy",
"pandas-stubs",
"pytest",
"scipy-stubs",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310,
},
600,
)
});
static FREQTRADE: Benchmark = Benchmark::new(
RealWorldProject {
name: "freqtrade",
repository: "https://github.com/freqtrade/freqtrade",
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
paths: &["freqtrade"],
dependencies: &[
"numpy",
"pandas-stubs",
"pydantic",
"sqlalchemy",
"types-cachetools",
"types-filelock",
"types-python-dateutil",
"types-requests",
"types-tabulate",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
400,
);
static FREQTRADE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "freqtrade",
repository: "https://github.com/freqtrade/freqtrade",
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
paths: vec![SystemPath::new("freqtrade")],
dependencies: vec![
"numpy",
"pandas-stubs",
"pydantic",
"sqlalchemy",
"types-cachetools",
"types-filelock",
"types-python-dateutil",
"types-requests",
"types-tabulate",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
400,
)
});
static PANDAS: Benchmark = Benchmark::new(
RealWorldProject {
name: "pandas",
repository: "https://github.com/pandas-dev/pandas",
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
paths: &["pandas"],
dependencies: &[
"numpy",
"types-python-dateutil",
"types-pytz",
"types-PyMySQL",
"types-setuptools",
"pytest",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
3000,
);
static PANDAS: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "pandas",
repository: "https://github.com/pandas-dev/pandas",
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
paths: vec![SystemPath::new("pandas")],
dependencies: vec![
"numpy",
"types-python-dateutil",
"types-pytz",
"types-PyMySQL",
"types-setuptools",
"pytest",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
3000,
)
});
static PYDANTIC: Benchmark = Benchmark::new(
RealWorldProject {
name: "pydantic",
repository: "https://github.com/pydantic/pydantic",
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
paths: &["pydantic"],
dependencies: &[
"annotated-types",
"pydantic-core",
"typing-extensions",
"typing-inspection",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY39,
},
1000,
);
static PYDANTIC: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "pydantic",
repository: "https://github.com/pydantic/pydantic",
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
paths: vec![SystemPath::new("pydantic")],
dependencies: vec![
"annotated-types",
"pydantic-core",
"typing-extensions",
"typing-inspection",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY39,
},
1000,
)
});
static SYMPY: Benchmark = Benchmark::new(
RealWorldProject {
name: "sympy",
repository: "https://github.com/sympy/sympy",
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
paths: &["sympy"],
dependencies: &["mpmath"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13000,
);
static SYMPY: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "sympy",
repository: "https://github.com/sympy/sympy",
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
paths: vec![SystemPath::new("sympy")],
dependencies: vec!["mpmath"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13000,
)
});
static TANJUN: Benchmark = Benchmark::new(
RealWorldProject {
name: "tanjun",
repository: "https://github.com/FasterSpeeding/Tanjun",
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
paths: &["tanjun"],
dependencies: &["hikari", "alluka"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
100,
);
static TANJUN: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "tanjun",
repository: "https://github.com/FasterSpeeding/Tanjun",
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
paths: vec![SystemPath::new("tanjun")],
dependencies: vec!["hikari", "alluka"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
100,
)
});
static STATIC_FRAME: Benchmark = Benchmark::new(
RealWorldProject {
name: "static-frame",
repository: "https://github.com/static-frame/static-frame",
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
paths: &["static_frame"],
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
// (seems to be built from source on the Codspeed CI runners for some reason).
dependencies: &["numpy"],
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
630,
);
static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "static-frame",
repository: "https://github.com/static-frame/static-frame",
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
paths: vec![SystemPath::new("static_frame")],
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
// (seems to be built from source on the Codspeed CI runners for some reason).
dependencies: vec!["numpy"],
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
600,
)
});
#[track_caller]
fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
@@ -238,22 +245,22 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
});
}
#[bench(args=[&ALTAIR, &FREQTRADE, &PYDANTIC, &TANJUN], sample_size=2, sample_count=3)]
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC, &*TANJUN], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS, &*STATIC_FRAME], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&SYMPY], sample_size=1, sample_count=2)]
#[bench(args=[&*SYMPY], sample_size=1, sample_count=2)]
fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&PYDANTIC], sample_size=3, sample_count=8)]
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=8)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();

View File

@@ -30,9 +30,9 @@ pub struct RealWorldProject<'a> {
/// Specific commit hash to checkout
pub commit: &'a str,
/// List of paths within the project to check (`ty check <paths>`)
pub paths: &'a [&'a str],
pub paths: Vec<&'a SystemPath>,
/// Dependencies to install via uv
pub dependencies: &'a [&'a str],
pub dependencies: Vec<&'a str>,
/// Limit candidate packages to those that were uploaded prior to a given point in time (ISO 8601 format).
/// Maps to uv's `exclude-newer`.
pub max_dep_date: &'a str,
@@ -125,9 +125,9 @@ impl<'a> InstalledProject<'a> {
&self.config
}
/// Get the benchmark paths
pub fn check_paths(&self) -> &[&str] {
self.config.paths
/// Get the benchmark paths as `SystemPathBuf`
pub fn check_paths(&self) -> &[&SystemPath] {
&self.config.paths
}
/// Get the virtual environment path
@@ -297,7 +297,7 @@ fn install_dependencies(checkout: &Checkout) -> Result<()> {
"--exclude-newer",
checkout.project().max_dep_date,
])
.args(checkout.project().dependencies);
.args(&checkout.project().dependencies);
let output = cmd
.output()

View File

@@ -227,32 +227,3 @@ async def read_thing(query: str):
@app.get("/things/{ thing_id : str }")
async def read_thing(query: str):
return {"query": query}
# https://github.com/astral-sh/ruff/issues/20680
# These should NOT trigger FAST003 because FastAPI doesn't recognize them as path parameters
# Non-ASCII characters in parameter name
@app.get("/f1/{用户身份}")
async def f1():
return locals()
# Space in parameter name
@app.get("/f2/{x: str}")
async def f2():
return locals()
# Non-ASCII converter
@app.get("/f3/{complex_number:}")
async def f3():
return locals()
# Mixed non-ASCII characters
@app.get("/f4/{用户_id}")
async def f4():
return locals()
# Space in parameter name with converter
@app.get("/f5/{param: int}")
async def f5():
return locals()

View File

@@ -1,8 +0,0 @@
import logging
variablename = "value"
log = logging.getLogger(__name__)
log.info(f"a" f"b {variablename}")
log.info("a " f"b {variablename}")
log.info("prefix " f"middle {variablename}" f" suffix")

View File

@@ -1,12 +1,12 @@
use std::iter::Peekable;
use std::ops::Range;
use std::sync::LazyLock;
use regex::{CaptureMatches, Regex};
use std::str::CharIndices;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprSubscript, Parameter, ParameterWithDefault};
use ruff_python_semantic::{BindingKind, Modules, ScopeKind, SemanticModel};
use ruff_python_stdlib::identifiers::is_identifier;
use ruff_text_size::{Ranged, TextSize};
use crate::Fix;
@@ -165,6 +165,11 @@ pub(crate) fn fastapi_unused_path_parameter(
// Check if any of the path parameters are not in the function signature.
for (path_param, range) in path_params {
// Ignore invalid identifiers (e.g., `user-id`, as opposed to `user_id`)
if !is_identifier(path_param) {
continue;
}
// If the path parameter is already in the function or the dependency signature,
// we don't need to do anything.
if named_args.contains(&path_param) {
@@ -456,19 +461,15 @@ fn parameter_alias<'a>(parameter: &'a Parameter, semantic: &SemanticModel) -> Op
/// the parameter name. For example, `/{x}` is a valid parameter, but `/{ x }` is treated literally.
#[derive(Debug)]
struct PathParamIterator<'a> {
inner: CaptureMatches<'a, 'a>,
input: &'a str,
chars: Peekable<CharIndices<'a>>,
}
impl<'a> PathParamIterator<'a> {
fn new(input: &'a str) -> Self {
/// Matches the Starlette pattern for path parameters with optional converters from
/// <https://github.com/Kludex/starlette/blob/e18637c68e36d112b1983bc0c8b663681e6a4c50/starlette/routing.py#L121>
static FASTAPI_PATH_PARAM_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::[a-zA-Z_][a-zA-Z0-9_]*)?\}").unwrap()
});
Self {
inner: FASTAPI_PATH_PARAM_REGEX.captures_iter(input),
PathParamIterator {
input,
chars: input.char_indices().peekable(),
}
}
}
@@ -477,10 +478,19 @@ impl<'a> Iterator for PathParamIterator<'a> {
type Item = (&'a str, Range<usize>);
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
// Extract the first capture group (the path parameter), but return the range of the
// whole match (everything in braces and including the braces themselves).
.and_then(|capture| Some((capture.get(1)?.as_str(), capture.get(0)?.range())))
while let Some((start, c)) = self.chars.next() {
if c == '{' {
if let Some((end, _)) = self.chars.by_ref().find(|&(_, ch)| ch == '}') {
let param_content = &self.input[start + 1..end];
// We ignore text after a colon, since those are path converters
// See also: https://fastapi.tiangolo.com/tutorial/path-params/?h=path#path-convertor
let param_name_end = param_content.find(':').unwrap_or(param_content.len());
let param_name = &param_content[..param_name_end];
return Some((param_name, start..end + 1));
}
}
}
None
}
}

View File

@@ -1091,12 +1091,9 @@ fn suspicious_function(
] => checker.report_diagnostic_if_enabled(SuspiciousInsecureCipherModeUsage, range),
// Mktemp
["tempfile", "mktemp"] => checker
.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
.map(|mut diagnostic| {
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
diagnostic
}),
["tempfile", "mktemp"] => {
checker.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
}
// Eval
["" | "builtins", "eval"] => {

View File

@@ -11,15 +11,15 @@ use crate::checkers::ast::Checker;
/// Checks for usage of `datetime.date.fromtimestamp()`.
///
/// ## Why is this bad?
/// Python date objects are naive, that is, not timezone-aware. While an aware
/// Python datetime objects can be naive or timezone-aware. While an aware
/// object represents a specific moment in time, a naive object does not
/// contain enough information to unambiguously locate itself relative to other
/// datetime objects. Since this can lead to errors, it is recommended to
/// always use timezone-aware objects.
///
/// `datetime.date.fromtimestamp(ts)` returns a naive date object.
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...).date()` to
/// create a timezone-aware datetime object and retrieve its date component.
/// `datetime.date.fromtimestamp(ts)` returns a naive datetime object.
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...)` to create a
/// timezone-aware object.
///
/// ## Example
/// ```python
@@ -32,14 +32,14 @@ use crate::checkers::ast::Checker;
/// ```python
/// import datetime
///
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc).date()
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc)
/// ```
///
/// Or, for Python 3.11 and later:
/// ```python
/// import datetime
///
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC).date()
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
/// ```
///
/// ## References

View File

@@ -11,15 +11,14 @@ use crate::checkers::ast::Checker;
/// Checks for usage of `datetime.date.today()`.
///
/// ## Why is this bad?
/// Python date objects are naive, that is, not timezone-aware. While an aware
/// Python datetime objects can be naive or timezone-aware. While an aware
/// object represents a specific moment in time, a naive object does not
/// contain enough information to unambiguously locate itself relative to other
/// datetime objects. Since this can lead to errors, it is recommended to
/// always use timezone-aware objects.
///
/// `datetime.date.today` returns a naive date object without taking timezones
/// into account. Instead, use `datetime.datetime.now(tz=...).date()` to
/// create a timezone-aware object and retrieve its date component.
/// `datetime.date.today` returns a naive datetime object. Instead, use
/// `datetime.datetime.now(tz=...).date()` to create a timezone-aware object.
///
/// ## Example
/// ```python

View File

@@ -42,9 +42,6 @@ use crate::rules::flake8_datetimez::helpers;
///
/// datetime.datetime.now(tz=datetime.UTC)
/// ```
///
/// ## References
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
#[derive(ViolationMetadata)]
pub(crate) struct CallDatetimeToday;

View File

@@ -41,9 +41,6 @@ use crate::rules::flake8_datetimez::helpers::{self, DatetimeModuleAntipattern};
///
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC)
/// ```
///
/// ## References
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
#[derive(ViolationMetadata)]
pub(crate) struct CallDatetimeWithoutTzinfo(DatetimeModuleAntipattern);

View File

@@ -38,9 +38,6 @@ use crate::checkers::ast::Checker;
///
/// datetime.datetime.max.replace(tzinfo=datetime.UTC)
/// ```
///
/// ## References
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
#[derive(ViolationMetadata)]
pub(crate) struct DatetimeMinMax {
min_max: MinMax,

View File

@@ -23,7 +23,6 @@ mod tests {
#[test_case(Path::new("G003.py"))]
#[test_case(Path::new("G004.py"))]
#[test_case(Path::new("G004_arg_order.py"))]
#[test_case(Path::new("G004_implicit_concat.py"))]
#[test_case(Path::new("G010.py"))]
#[test_case(Path::new("G101_1.py"))]
#[test_case(Path::new("G101_2.py"))]
@@ -53,7 +52,6 @@ mod tests {
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
#[test_case(Rule::LoggingFString, Path::new("G004_implicit_concat.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@@ -42,52 +42,38 @@ fn logging_f_string(
// Default to double quotes if we can't determine it.
let quote_str = f_string
.value
.iter()
.map(|part| match part {
ast::FStringPart::Literal(literal) => literal.flags.quote_str(),
ast::FStringPart::FString(f) => f.flags.quote_str(),
})
.f_strings()
.next()
.map(|f| f.flags.quote_str())
.unwrap_or("\"");
for part in &f_string.value {
match part {
ast::FStringPart::Literal(literal) => {
let literal_text = literal.as_str();
if literal_text.contains('%') {
return;
for f in f_string.value.f_strings() {
for element in &f.elements {
match element {
InterpolatedStringElement::Literal(lit) => {
// If the literal text contains a '%' placeholder, bail out: mixing
// f-string interpolation with '%' placeholders is ambiguous for our
// automatic conversion, so don't offer a fix for this case.
if lit.value.as_ref().contains('%') {
return;
}
format_string.push_str(lit.value.as_ref());
}
format_string.push_str(literal_text);
}
ast::FStringPart::FString(f) => {
for element in &f.elements {
match element {
InterpolatedStringElement::Literal(lit) => {
// If the literal text contains a '%' placeholder, bail out: mixing
// f-string interpolation with '%' placeholders is ambiguous for our
// automatic conversion, so don't offer a fix for this case.
if lit.value.as_ref().contains('%') {
return;
}
format_string.push_str(lit.value.as_ref());
}
InterpolatedStringElement::Interpolation(interpolated) => {
if interpolated.format_spec.is_some()
|| !matches!(
interpolated.conversion,
ruff_python_ast::ConversionFlag::None
)
{
return;
}
match interpolated.expression.as_ref() {
Expr::Name(name) => {
format_string.push_str("%s");
args.push(name.id.as_str());
}
_ => return,
}
InterpolatedStringElement::Interpolation(interpolated) => {
if interpolated.format_spec.is_some()
|| !matches!(
interpolated.conversion,
ruff_python_ast::ConversionFlag::None
)
{
return;
}
match interpolated.expression.as_ref() {
Expr::Name(name) => {
format_string.push_str("%s");
args.push(name.id.as_str());
}
_ => return,
}
}
}

View File

@@ -1,35 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
assertion_line: 50
---
G004 Logging statement uses f-string
--> G004_implicit_concat.py:6:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_implicit_concat.py:7:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_implicit_concat.py:8:10
|
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@@ -1,53 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
assertion_line: 71
---
G004 [*] Logging statement uses f-string
--> G004_implicit_concat.py:6:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
3 | variablename = "value"
4 |
5 | log = logging.getLogger(__name__)
- log.info(f"a" f"b {variablename}")
6 + log.info("ab %s", variablename)
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
G004 [*] Logging statement uses f-string
--> G004_implicit_concat.py:7:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
4 |
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
- log.info("a " f"b {variablename}")
7 + log.info("a b %s", variablename)
8 | log.info("prefix " f"middle {variablename}" f" suffix")
G004 [*] Logging statement uses f-string
--> G004_implicit_concat.py:8:10
|
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
- log.info("prefix " f"middle {variablename}" f" suffix")
8 + log.info("prefix middle %s suffix", variablename)

View File

@@ -74,8 +74,7 @@ pub(crate) fn bytestring_attribute(checker: &Checker, attribute: &Expr) {
["collections", "abc", "ByteString"] => ByteStringOrigin::CollectionsAbc,
_ => return,
};
let mut diagnostic = checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
}
/// PYI057
@@ -95,9 +94,7 @@ pub(crate) fn bytestring_import(checker: &Checker, import_from: &ast::StmtImport
for name in names {
if name.name.as_str() == "ByteString" {
let mut diagnostic =
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
}
}
}

View File

@@ -898,9 +898,7 @@ fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorato
/// PT020
fn check_fixture_decorator_name(checker: &Checker, decorator: &Decorator) {
if is_pytest_yield_fixture(decorator, checker.semantic()) {
let mut diagnostic =
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
}
}

View File

@@ -223,7 +223,7 @@ enum Argumentable {
impl Argumentable {
fn check_for(self, checker: &Checker, name: String, range: TextRange) {
let mut diagnostic = match self {
match self {
Self::Function => checker.report_diagnostic(UnusedFunctionArgument { name }, range),
Self::Method => checker.report_diagnostic(UnusedMethodArgument { name }, range),
Self::ClassMethod => {
@@ -234,7 +234,6 @@ impl Argumentable {
}
Self::Lambda => checker.report_diagnostic(UnusedLambdaArgument { name }, range),
};
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
}
const fn rule_code(self) -> Rule {

View File

@@ -80,7 +80,6 @@ pub(crate) fn deprecated_function(checker: &Checker, expr: &Expr) {
},
expr.range(),
);
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from("numpy", replacement),

View File

@@ -80,7 +80,6 @@ pub(crate) fn deprecated_type_alias(checker: &Checker, expr: &Expr) {
},
expr.range(),
);
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
let type_name = match type_name {
"unicode" => "str",
_ => type_name,

View File

@@ -15,16 +15,14 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::HasDefinition;
use ty_python_semantic::ImportAliasResolution;
use ty_python_semantic::ResolvedDefinition;
use ty_python_semantic::types::Type;
use ty_python_semantic::types::ide_support::{
call_signature_details, definitions_for_keyword_argument,
};
use ty_python_semantic::types::definitions_for_keyword_argument;
use ty_python_semantic::types::{Type, call_signature_details};
use ty_python_semantic::{
HasType, SemanticModel, definitions_for_imported_symbol, definitions_for_name,
};
#[derive(Clone, Debug)]
pub(crate) enum GotoTarget<'a> {
pub enum GotoTarget<'a> {
Expression(ast::ExprRef<'a>),
FunctionDef(&'a ast::StmtFunctionDef),
ClassDef(&'a ast::StmtClassDef),
@@ -271,7 +269,7 @@ impl<'db> DefinitionsOrTargets<'db> {
}
impl GotoTarget<'_> {
pub(crate) fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
pub fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
let ty = match self {
GotoTarget::Expression(expression) => expression.inferred_type(model),
GotoTarget::FunctionDef(function) => function.inferred_type(model),
@@ -822,10 +820,7 @@ fn definitions_to_navigation_targets<'db>(
}
}
pub(crate) fn find_goto_target(
parsed: &ParsedModuleRef,
offset: TextSize,
) -> Option<GotoTarget<'_>> {
pub fn find_goto_target(parsed: &ParsedModuleRef, offset: TextSize) -> Option<GotoTarget<'_>> {
let token = parsed
.tokens()
.at_offset(offset)

View File

@@ -308,8 +308,26 @@ mod tests {
"#,
);
// TODO: This should jump to the definition of `Alias` above.
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> main.py:4:1
|
2 | from typing_extensions import TypeAliasType
3 |
4 | Alias = TypeAliasType("Alias", tuple[int, int])
| ^^^^^
5 |
6 | Alias
|
info: Source
--> main.py:6:1
|
4 | Alias = TypeAliasType("Alias", tuple[int, int])
5 |
6 | Alias
| ^^^^^
|
"#);
}
#[test]

View File

@@ -6,8 +6,7 @@ use ruff_db::parsed::parsed_module;
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::types::Type;
use ty_python_semantic::types::ide_support::inlay_hint_function_argument_details;
use ty_python_semantic::types::{Type, inlay_hint_function_argument_details};
use ty_python_semantic::{HasType, SemanticModel};
#[derive(Debug, Clone)]

View File

@@ -30,7 +30,7 @@ pub use all_symbols::{AllSymbolInfo, all_symbols};
pub use completion::{Completion, CompletionKind, CompletionSettings, completion};
pub use doc_highlights::document_highlights;
pub use document_symbols::document_symbols;
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
pub use goto::{find_goto_target, goto_declaration, goto_definition, goto_type_definition};
pub use goto_references::goto_references;
pub use hover::hover;
pub use inlay_hints::{InlayHintKind, InlayHintLabel, InlayHintSettings, inlay_hints};

View File

@@ -13,8 +13,9 @@ use ruff_python_ast::{
use ruff_text_size::{Ranged, TextLen, TextRange};
use std::ops::Deref;
use ty_python_semantic::{
HasType, SemanticModel, semantic_index::definition::DefinitionKind, types::Type,
types::ide_support::definition_kind_for_name,
HasType, SemanticModel,
semantic_index::definition::DefinitionKind,
types::{Type, definition_kind_for_name},
};
// This module walks the AST and collects a set of "semantic tokens" for a file

View File

@@ -17,7 +17,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::ResolvedDefinition;
use ty_python_semantic::SemanticModel;
use ty_python_semantic::semantic_index::definition::Definition;
use ty_python_semantic::types::ide_support::{
use ty_python_semantic::types::{
CallSignatureDetails, call_signature_details, find_active_signature_from_details,
};

View File

@@ -130,9 +130,13 @@ type IntList = list[int]
m: IntList = [1, 2, 3]
reveal_type(m) # revealed: list[int]
# TODO: this should type-check and avoid literal promotion
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
# TODO: this should type-check and avoid literal promotion
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
o: list[typing.LiteralString] = ["a", "b", "c"]
reveal_type(o) # revealed: list[LiteralString]
@@ -146,75 +150,7 @@ r: dict[int | str, int | str] = {1: 1, 2: 2, 3: 3}
reveal_type(r) # revealed: dict[int | str, int | str]
```
## Optional collection literal annotations are understood
```toml
[environment]
python-version = "3.12"
```
```py
import typing
a: list[int] | None = [1, 2, 3]
reveal_type(a) # revealed: list[int]
b: list[int | str] | None = [1, 2, 3]
reveal_type(b) # revealed: list[int | str]
c: typing.List[int] | None = [1, 2, 3]
reveal_type(c) # revealed: list[int]
d: list[typing.Any] | None = []
reveal_type(d) # revealed: list[Any]
e: set[int] | None = {1, 2, 3}
reveal_type(e) # revealed: set[int]
f: set[int | str] | None = {1, 2, 3}
reveal_type(f) # revealed: set[int | str]
g: typing.Set[int] | None = {1, 2, 3}
reveal_type(g) # revealed: set[int]
h: list[list[int]] | None = [[], [42]]
reveal_type(h) # revealed: list[list[int]]
i: list[typing.Any] | None = [1, 2, "3", ([4],)]
reveal_type(i) # revealed: list[Any | int | str | tuple[list[Unknown | int]]]
j: list[tuple[str | int, ...]] | None = [(1, 2), ("foo", "bar"), ()]
reveal_type(j) # revealed: list[tuple[str | int, ...]]
k: list[tuple[list[int], ...]] | None = [([],), ([1, 2], [3, 4]), ([5], [6], [7])]
reveal_type(k) # revealed: list[tuple[list[int], ...]]
l: tuple[list[int], *tuple[list[typing.Any], ...], list[str]] | None = ([1, 2, 3], [4, 5, 6], [7, 8, 9], ["10", "11", "12"])
# TODO: this should be `tuple[list[int], list[Any | int], list[Any | int], list[str]]`
reveal_type(l) # revealed: tuple[list[Unknown | int], list[Unknown | int], list[Unknown | int], list[Unknown | str]]
type IntList = list[int]
m: IntList | None = [1, 2, 3]
reveal_type(m) # revealed: list[int]
n: list[typing.Literal[1, 2, 3]] | None = [1, 2, 3]
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
o: list[typing.LiteralString] | None = ["a", "b", "c"]
reveal_type(o) # revealed: list[LiteralString]
p: dict[int, int] | None = {}
reveal_type(p) # revealed: dict[int, int]
q: dict[int | str, int] | None = {1: 1, 2: 2, 3: 3}
reveal_type(q) # revealed: dict[int | str, int]
r: dict[int | str, int | str] | None = {1: 1, 2: 2, 3: 3}
reveal_type(r) # revealed: dict[int | str, int | str]
```
## Incorrect collection literal assignments are complained about
## Incorrect collection literal assignments are complained aobut
```py
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[str]`"
@@ -224,81 +160,6 @@ a: list[str] = [1, 2, 3]
b: set[int] = {1, 2, "3"}
```
## Literal annnotations are respected
```toml
[environment]
python-version = "3.12"
```
```py
from enum import Enum
from typing_extensions import Literal, LiteralString
a: list[Literal[1]] = [1]
reveal_type(a) # revealed: list[Literal[1]]
b: list[Literal[True]] = [True]
reveal_type(b) # revealed: list[Literal[True]]
c: list[Literal["a"]] = ["a"]
reveal_type(c) # revealed: list[Literal["a"]]
d: list[LiteralString] = ["a", "b", "c"]
reveal_type(d) # revealed: list[LiteralString]
e: list[list[Literal[1]]] = [[1]]
reveal_type(e) # revealed: list[list[Literal[1]]]
class Color(Enum):
RED = "red"
f: dict[list[Literal[1]], list[Literal[Color.RED]]] = {[1]: [Color.RED, Color.RED]}
reveal_type(f) # revealed: dict[list[Literal[1]], list[Literal[Color.RED]]]
class X[T]:
def __init__(self, value: T): ...
g: X[Literal[1]] = X(1)
reveal_type(g) # revealed: X[Literal[1]]
h: X[int] = X(1)
reveal_type(h) # revealed: X[int]
i: dict[list[X[Literal[1]]], set[Literal[b"a"]]] = {[X(1)]: {b"a"}}
reveal_type(i) # revealed: dict[list[X[Literal[1]]], set[Literal[b"a"]]]
j: list[Literal[1, 2, 3]] = [1, 2, 3]
reveal_type(j) # revealed: list[Literal[1, 2, 3]]
k: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
reveal_type(k) # revealed: list[Literal[1, 2, 3]]
type Y[T] = list[T]
l: Y[Y[Literal[1]]] = [[1]]
reveal_type(l) # revealed: list[list[Literal[1]]]
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
n: list[tuple[int, str, int]] = [(1, "2", 3), (4, "5", 6)]
reveal_type(n) # revealed: list[tuple[int, str, int]]
o: list[tuple[Literal[1], ...]] = [(1, 1, 1)]
reveal_type(o) # revealed: list[tuple[Literal[1], ...]]
p: list[tuple[int, ...]] = [(1, 1, 1)]
reveal_type(p) # revealed: list[tuple[int, ...]]
# literal promotion occurs based on assignability, an exact match is not required
q: list[int | Literal[1]] = [1]
reveal_type(q) # revealed: list[int]
r: list[Literal[1, 2, 3, 4]] = [1, 2]
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
```
## PEP-604 annotations are supported
```py
@@ -376,22 +237,6 @@ x = Foo()
reveal_type(x) # revealed: Foo
```
## Annotations are deferred by default in Python 3.14 and later
```toml
[environment]
python-version = "3.14"
```
```py
x: Foo
class Foo: ...
x = Foo()
reveal_type(x) # revealed: Foo
```
## Annotated assignments in stub files are inferred correctly
```pyi

View File

@@ -820,30 +820,22 @@ reveal_type(C().c) # revealed: int
### Inheritance of class/instance attributes
#### Instance variable defined in a base class
```py
class Base:
attribute: int | None = 1
declared_in_body: int | None = 1
redeclared_with_same_type: str | None
redeclared_with_narrower_type: str | None
redeclared_with_wider_type: str | None
overwritten_in_subclass_body: str
overwritten_in_subclass_method: str
undeclared = "base"
base_class_attribute_1: str | None
base_class_attribute_2: str | None
base_class_attribute_3: str | None
def __init__(self) -> None:
self.pure_attribute: str | None = "value in base"
self.pure_overwritten_in_subclass_body: str = "value in base"
self.pure_overwritten_in_subclass_method: str = "value in base"
self.pure_undeclared = "base"
self.defined_in_init: str | None = "value in base"
class Intermediate(Base):
# Redeclaring base class attributes with the *same *type is fine:
redeclared_with_same_type: str | None = None
base_class_attribute_1: str | None = None
# Redeclaring them with a *narrower type* is unsound, because modifications
# through a `Base` reference could violate that constraint.
@@ -855,67 +847,22 @@ class Intermediate(Base):
# enabled by default can still be discussed.
#
# TODO: This should be an error
redeclared_with_narrower_type: str
base_class_attribute_2: str
# Redeclaring attributes with a *wider type* directly violates LSP.
#
# In this case, both mypy and pyright report an error.
#
# TODO: This should be an error
redeclared_with_wider_type: str | int | None
# TODO: This should be an `invalid-assignment` error
overwritten_in_subclass_body = None
# TODO: This should be an `invalid-assignment` error
pure_overwritten_in_subclass_body = None
undeclared = "intermediate"
def set_attributes(self) -> None:
# TODO: This should be an `invalid-assignment` error
self.overwritten_in_subclass_method = None
# TODO: This should be an `invalid-assignment` error
self.pure_overwritten_in_subclass_method = None
self.pure_undeclared = "intermediate"
base_class_attribute_3: str | int | None
class Derived(Intermediate): ...
reveal_type(Derived.attribute) # revealed: int | None
reveal_type(Derived().attribute) # revealed: int | None
reveal_type(Derived.declared_in_body) # revealed: int | None
reveal_type(Derived.redeclared_with_same_type) # revealed: str | None
reveal_type(Derived().redeclared_with_same_type) # revealed: str | None
reveal_type(Derived().declared_in_body) # revealed: int | None
# TODO: It would probably be more consistent if these were `str | None`
reveal_type(Derived.redeclared_with_narrower_type) # revealed: str
reveal_type(Derived().redeclared_with_narrower_type) # revealed: str
# TODO: It would probably be more consistent if these were `str | None`
reveal_type(Derived.redeclared_with_wider_type) # revealed: str | int | None
reveal_type(Derived().redeclared_with_wider_type) # revealed: str | int | None
# TODO: Both of these should be `str`
reveal_type(Derived.overwritten_in_subclass_body) # revealed: Unknown | None
reveal_type(Derived().overwritten_in_subclass_body) # revealed: Unknown | None | str
reveal_type(Derived.overwritten_in_subclass_method) # revealed: str
reveal_type(Derived().overwritten_in_subclass_method) # revealed: str
reveal_type(Derived().pure_attribute) # revealed: str | None
# TODO: This should be `str`
reveal_type(Derived().pure_overwritten_in_subclass_body) # revealed: Unknown | None | str
reveal_type(Derived().pure_overwritten_in_subclass_method) # revealed: str
# TODO: Both of these should be `Unknown | Literal["intermediate", "base"]`
reveal_type(Derived.undeclared) # revealed: Unknown | Literal["intermediate"]
reveal_type(Derived().undeclared) # revealed: Unknown | Literal["intermediate"]
reveal_type(Derived().pure_undeclared) # revealed: Unknown | Literal["intermediate", "base"]
reveal_type(Derived().defined_in_init) # revealed: str | None
```
## Accessing attributes on class objects

View File

@@ -1,147 +0,0 @@
# Bidirectional type inference
ty partially supports bidirectional type inference. This is a mechanism for inferring the type of an
expression "from the outside in". Normally, type inference proceeds "from the inside out". That is,
in order to infer the type of an expression, the types of all sub-expressions must first be
inferred. There is no reverse dependency. However, when performing complex type inference, such as
when generics are involved, the type of an outer expression can sometimes be useful in inferring
inner expressions. Bidirectional type inference is a mechanism that propagates such "expected types"
to the inference of inner expressions.
## Propagating target type annotation
```toml
[environment]
python-version = "3.12"
```
```py
def list1[T](x: T) -> list[T]:
return [x]
l1 = list1(1)
reveal_type(l1) # revealed: list[Literal[1]]
l2: list[int] = list1(1)
reveal_type(l2) # revealed: list[int]
# `list[Literal[1]]` and `list[int]` are incompatible, since `list[T]` is invariant in `T`.
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
l2 = l1
intermediate = list1(1)
# TODO: the error will not occur if we can infer the type of `intermediate` to be `list[int]`
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
l3: list[int] = intermediate
# TODO: it would be nice if this were `list[int]`
reveal_type(intermediate) # revealed: list[Literal[1]]
reveal_type(l3) # revealed: list[int]
l4: list[int | str] | None = list1(1)
reveal_type(l4) # revealed: list[int | str]
def _(l: list[int] | None = None):
l1 = l or list()
reveal_type(l1) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
l2: list[int] = l or list()
# it would be better if this were `list[int]`? (https://github.com/astral-sh/ty/issues/136)
reveal_type(l2) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
def f[T](x: T, cond: bool) -> T | list[T]:
return x if cond else [x]
# TODO: no error
# error: [invalid-assignment] "Object of type `Literal[1] | list[Literal[1]]` is not assignable to `int | list[int]`"
l5: int | list[int] = f(1, True)
```
`typed_dict.py`:
```py
from typing import TypedDict
class TD(TypedDict):
x: int
d1 = {"x": 1}
d2: TD = {"x": 1}
d3: dict[str, int] = {"x": 1}
reveal_type(d1) # revealed: dict[Unknown | str, Unknown | int]
reveal_type(d2) # revealed: TD
reveal_type(d3) # revealed: dict[str, int]
def _() -> TD:
return {"x": 1}
def _() -> TD:
# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
return {}
```
## Propagating return type annotation
```toml
[environment]
python-version = "3.12"
```
```py
from typing import overload, Callable
def list1[T](x: T) -> list[T]:
return [x]
def get_data() -> dict | None:
return {}
def wrap_data() -> list[dict]:
if not (res := get_data()):
return list1({})
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
# `list[dict[Unknown, Unknown] & ~AlwaysFalsy]` and `list[dict[Unknown, Unknown]]` are incompatible,
# but the return type check passes here because the type of `list1(res)` is inferred
# by bidirectional type inference using the annotated return type, and the type of `res` is not used.
return list1(res)
def wrap_data2() -> list[dict] | None:
if not (res := get_data()):
return None
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
return list1(res)
def deco[T](func: Callable[[], T]) -> Callable[[], T]:
return func
def outer() -> Callable[[], list[dict]]:
@deco
def inner() -> list[dict]:
if not (res := get_data()):
return list1({})
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
return list1(res)
return inner
@overload
def f(x: int) -> list[int]: ...
@overload
def f(x: str) -> list[str]: ...
def f(x: int | str) -> list[int] | list[str]:
# `list[int] | list[str]` is disjoint from `list[int | str]`.
if isinstance(x, int):
return list1(x)
else:
return list1(x)
reveal_type(f(1)) # revealed: list[int]
reveal_type(f("a")) # revealed: list[str]
async def g() -> list[int | str]:
return list1(1)
def h[T](x: T, cond: bool) -> T | list[T]:
return i(x, cond)
def i[T](x: T, cond: bool) -> T | list[T]:
return x if cond else [x]
```

View File

@@ -689,7 +689,7 @@ def _(
# revealed: (obj: type) -> None
reveal_type(e)
# revealed: (fget: ((Any, /) -> Any) | None = EllipsisType, fset: ((Any, Any, /) -> None) | None = EllipsisType, fdel: ((Any, /) -> None) | None = EllipsisType, doc: str | None = EllipsisType) -> property
# revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> Any) | None = None, doc: str | None = None) -> Unknown
reveal_type(f)
# revealed: Overload[(self: property, instance: None, owner: type, /) -> Unknown, (self: property, instance: object, owner: type | None = None, /) -> Unknown]

View File

@@ -99,6 +99,8 @@ If the arity check only matches a single overload, it should be evaluated as a r
call should be reported directly and not as a `no-matching-overload` error.
```py
from typing_extensions import reveal_type
from overloaded import f
reveal_type(f()) # revealed: None
@@ -1208,7 +1210,11 @@ from typing_extensions import LiteralString
def f(a: Foo, b: list[str], c: list[LiteralString], e):
reveal_type(e) # revealed: Unknown
reveal_type(a.join(b)) # revealed: str
# TODO: we should select the second overload here and reveal `str`
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
reveal_type(a.join(b)) # revealed: LiteralString
reveal_type(a.join(c)) # revealed: LiteralString
# since both overloads match and they have return types that are not equivalent,

View File

@@ -14,16 +14,9 @@ common usage.
### Explicit Super Object
<!-- snapshot-diagnostics -->
`super(pivot_class, owner)` performs attribute lookup along the MRO, starting immediately after the
specified pivot class.
```toml
[environment]
python-version = "3.12"
```
```py
class A:
def a(self): ...
@@ -41,15 +34,21 @@ reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>,
super(C, C()).a
super(C, C()).b
super(C, C()).c # error: [unresolved-attribute]
# error: [unresolved-attribute] "Type `<super: <class 'C'>, C>` has no attribute `c`"
super(C, C()).c
super(B, C()).a
super(B, C()).b # error: [unresolved-attribute]
super(B, C()).c # error: [unresolved-attribute]
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `b`"
super(B, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `c`"
super(B, C()).c
super(A, C()).a # error: [unresolved-attribute]
super(A, C()).b # error: [unresolved-attribute]
super(A, C()).c # error: [unresolved-attribute]
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `a`"
super(A, C()).a
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `b`"
super(A, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `c`"
super(A, C()).c
reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
@@ -57,80 +56,12 @@ reveal_type(super(C, C()).aa) # revealed: int
reveal_type(super(C, C()).bb) # revealed: int
```
Examples of explicit `super()` with unusual types. We allow almost any type to be passed as the
second argument to `super()` -- the only exceptions are "pure abstract" types such as `Callable` and
synthesized `Protocol`s that cannot be upcast to, or interpreted as, a non-`object` nominal type.
```py
import types
from typing_extensions import Callable, TypeIs, Literal, TypedDict
def f(): ...
class Foo[T]:
def method(self): ...
@property
def some_property(self): ...
type Alias = int
class SomeTypedDict(TypedDict):
x: int
y: bytes
# revealed: <super: <class 'object'>, FunctionType>
reveal_type(super(object, f))
# revealed: <super: <class 'object'>, WrapperDescriptorType>
reveal_type(super(object, types.FunctionType.__get__))
# revealed: <super: <class 'object'>, GenericAlias>
reveal_type(super(object, Foo[int]))
# revealed: <super: <class 'object'>, _SpecialForm>
reveal_type(super(object, Literal))
# revealed: <super: <class 'object'>, TypeAliasType>
reveal_type(super(object, Alias))
# revealed: <super: <class 'object'>, MethodType>
reveal_type(super(object, Foo().method))
# revealed: <super: <class 'object'>, property>
reveal_type(super(object, Foo.some_property))
def g(x: object) -> TypeIs[list[object]]:
return isinstance(x, list)
def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
if hasattr(x, "bar"):
# revealed: <Protocol with members 'bar'>
reveal_type(x)
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super(object, x))
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super(object, z))
is_list = g(x)
# revealed: TypeIs[list[object] @ x]
reveal_type(is_list)
# revealed: <super: <class 'object'>, bool>
reveal_type(super(object, is_list))
# revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
reveal_type(super(object, y))
```
### Implicit Super Object
<!-- snapshot-diagnostics -->
The implicit form `super()` is same as `super(__class__, <first argument>)`. The `__class__` refers
to the class that contains the function where `super()` is used. The first argument refers to the
current methods first parameter (typically `self` or `cls`).
```toml
[environment]
python-version = "3.12"
```
```py
from __future__ import annotations
@@ -143,7 +74,6 @@ class B(A):
def __init__(self, a: int):
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
super().__init__(a)
@classmethod
@@ -156,123 +86,6 @@ super(B, B(42)).__init__(42)
super(B, B).f()
```
Some examples with unusual annotations for `self` or `cls`:
```py
import enum
from typing import Any, Self, Never, Protocol, Callable
from ty_extensions import Intersection
class BuilderMeta(type):
def __new__(
cls: type[Any],
name: str,
bases: tuple[type, ...],
dct: dict[str, Any],
) -> BuilderMeta:
# revealed: <super: <class 'BuilderMeta'>, Any>
s = reveal_type(super())
# revealed: Any
return reveal_type(s.__new__(cls, name, bases, dct))
class BuilderMeta2(type):
def __new__(
cls: type[BuilderMeta2],
name: str,
bases: tuple[type, ...],
dct: dict[str, Any],
) -> BuilderMeta2:
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
s = reveal_type(super())
# TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
# revealed: Unknown
return reveal_type(s.__new__(cls, name, bases, dct))
class Foo[T]:
x: T
def method(self: Any):
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
if isinstance(self, Foo):
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
def method2(self: Foo[T]):
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
reveal_type(super())
def method3(self: Foo):
# revealed: <super: <class 'Foo'>, Foo[Unknown]>
reveal_type(super())
def method4(self: Self):
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
reveal_type(super())
def method5[S: Foo[int]](self: S, other: S) -> S:
# revealed: <super: <class 'Foo'>, Foo[int]>
reveal_type(super())
return self
def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
# revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
reveal_type(super())
return self
def method7[S](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
def method8[S: int](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
def method9[S: (int, str)](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
def method10[S: Callable[..., str]](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
type Alias = Bar
class Bar:
def method(self: Alias):
# revealed: <super: <class 'Bar'>, Bar>
reveal_type(super())
def pls_dont_call_me(self: Never):
# revealed: <super: <class 'Bar'>, Unknown>
reveal_type(super())
def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
# revealed: <super: <class 'Bar'>, Bar>
reveal_type(super())
class P(Protocol):
def method(self: P):
# revealed: <super: <class 'P'>, P>
reveal_type(super())
class E(enum.Enum):
X = 1
def method(self: E):
match self:
case E.X:
# revealed: <super: <class 'E'>, E>
reveal_type(super())
```
### Unbound Super Object
Calling `super(cls)` without a second argument returns an _unbound super object_. This is treated as
@@ -354,19 +167,11 @@ class A:
## Built-ins and Literals
```py
from enum import Enum
reveal_type(super(bool, True)) # revealed: <super: <class 'bool'>, bool>
reveal_type(super(bool, bool())) # revealed: <super: <class 'bool'>, bool>
reveal_type(super(int, bool())) # revealed: <super: <class 'int'>, bool>
reveal_type(super(int, 3)) # revealed: <super: <class 'int'>, int>
reveal_type(super(str, "")) # revealed: <super: <class 'str'>, str>
reveal_type(super(bytes, b"")) # revealed: <super: <class 'bytes'>, bytes>
class E(Enum):
X = 42
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
```
## Descriptor Behavior with Super
@@ -537,7 +342,7 @@ def f(x: int):
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
super(IntAlias, 0)
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
# error: [invalid-super-argument] "`Literal[""]` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, Literal[""])` call"
# revealed: Unknown
reveal_type(super(int, str()))

View File

@@ -67,7 +67,7 @@ class C:
name: str
```
## Types of dataclass-transformers
## Types of decorators
The examples from this section are straight from the Python documentation on
[`typing.dataclass_transform`].
@@ -165,7 +165,7 @@ Normal(1) < Normal(2) # error: [unsupported-operator]
class NormalOverwritten:
inner: int
reveal_type(NormalOverwritten(1) < NormalOverwritten(2)) # revealed: bool
NormalOverwritten(1) < NormalOverwritten(2)
@order_default_false
class OrderFalse:
@@ -177,13 +177,13 @@ OrderFalse(1) < OrderFalse(2) # error: [unsupported-operator]
class OrderFalseOverwritten:
inner: int
reveal_type(OrderFalseOverwritten(1) < OrderFalseOverwritten(2)) # revealed: bool
OrderFalseOverwritten(1) < OrderFalseOverwritten(2)
@order_default_true
class OrderTrue:
inner: int
reveal_type(OrderTrue(1) < OrderTrue(2)) # revealed: bool
OrderTrue(1) < OrderTrue(2)
@order_default_true(order=False)
class OrderTrueOverwritten:
@@ -193,36 +193,6 @@ class OrderTrueOverwritten:
OrderTrueOverwritten(1) < OrderTrueOverwritten(2)
```
This also works for metaclass-based transformers:
```py
@dataclass_transform(order_default=True)
class OrderedModelMeta(type): ...
class OrderedModel(metaclass=OrderedModelMeta): ...
class TestWithMeta(OrderedModel):
inner: int
reveal_type(TestWithMeta(1) < TestWithMeta(2)) # revealed: bool
```
And for base-class-based transformers:
```py
@dataclass_transform(order_default=True)
class OrderedModelBase: ...
class TestWithBase(OrderedModelBase):
inner: int
# TODO: No errors here, should reveal `bool`
# error: [too-many-positional-arguments]
# error: [too-many-positional-arguments]
# error: [unsupported-operator]
reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: Unknown
```
### `kw_only_default`
When provided, sets the default value for the `kw_only` parameter of `field()`.
@@ -232,7 +202,7 @@ from typing import dataclass_transform
from dataclasses import field
@dataclass_transform(kw_only_default=True)
def create_model(*, kw_only: bool = True): ...
def create_model(*, init=True): ...
@create_model()
class A:
name: str
@@ -243,252 +213,27 @@ a = A(name="Harry")
a = A("Harry")
```
This can be overridden by setting `kw_only=False` when applying the decorator:
TODO: This can be overridden by the call to the decorator function.
```py
from typing import dataclass_transform
@dataclass_transform(kw_only_default=True)
def create_model(*, kw_only: bool = True): ...
@create_model(kw_only=False)
class CustomerModel:
id: int
name: str
# TODO: Should not emit errors
# error: [missing-argument]
# error: [too-many-positional-arguments]
c = CustomerModel(1, "Harry")
```
This also works for metaclass-based transformers:
### `field_specifiers`
```py
@dataclass_transform(kw_only_default=True)
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
class TestMeta(ModelBase):
name: str
reveal_type(TestMeta.__init__) # revealed: (self: TestMeta, *, name: str) -> None
```
And for base-class-based transformers:
```py
@dataclass_transform(kw_only_default=True)
class ModelBase: ...
class TestBase(ModelBase):
name: str
# TODO: This should be `(self: TestBase, *, name: str) -> None`
reveal_type(TestBase.__init__) # revealed: def __init__(self) -> None
```
### `frozen_default`
When provided, sets the default value for the `frozen` parameter of `field()`.
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
def create_model(*, frozen: bool = True): ...
@create_model()
class ImmutableModel:
name: str
i = ImmutableModel(name="test")
i.name = "new" # error: [invalid-assignment]
```
Again, this can be overridden by setting `frozen=False` when applying the decorator:
```py
@create_model(frozen=False)
class MutableModel:
name: str
m = MutableModel(name="test")
m.name = "new" # No error
```
This also works for metaclass-based transformers:
```py
@dataclass_transform(frozen_default=True)
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
class TestMeta(ModelBase):
name: str
t = TestMeta(name="test")
t.name = "new" # error: [invalid-assignment]
```
And for base-class-based transformers:
```py
@dataclass_transform(frozen_default=True)
class ModelBase: ...
class TestMeta(ModelBase):
name: str
# TODO: no error here
# error: [unknown-argument]
t = TestMeta(name="test")
# TODO: this should be an `invalid-assignment` error
t.name = "new"
```
### Combining parameters
Combining several of these parameters also works as expected:
```py
from typing import dataclass_transform
@dataclass_transform(eq_default=True, order_default=False, kw_only_default=True, frozen_default=True)
def create_model(*, eq: bool = True, order: bool = False, kw_only: bool = True, frozen: bool = True): ...
@create_model(eq=False, order=True, kw_only=False, frozen=False)
class OverridesAllParametersModel:
name: str
age: int
# Positional arguments are allowed:
model = OverridesAllParametersModel("test", 25)
# Mutation is allowed:
model.name = "new" # No error
# Comparison methods are generated:
model < model # No error
```
### Overwriting of default parameters on the dataclass-like class
#### Using function-based transformers
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
def default_frozen_model(*, frozen: bool = True): ...
@default_frozen_model()
class Frozen:
name: str
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
@default_frozen_model(frozen=False)
class Mutable:
name: str
m = Mutable(name="test")
m.name = "new" # No error
```
#### Using metaclass-based transformers
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
class DefaultFrozenMeta(type):
def __new__(
cls,
name,
bases,
namespace,
*,
frozen: bool = True,
): ...
class DefaultFrozenModel(metaclass=DefaultFrozenMeta): ...
class Frozen(DefaultFrozenModel):
name: str
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
class Mutable(DefaultFrozenModel, frozen=False):
name: str
m = Mutable(name="test")
# TODO: no error here
m.name = "new" # error: [invalid-assignment]
```
#### Using base-class-based transformers
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
class DefaultFrozenModel:
def __init_subclass__(
cls,
*,
frozen: bool = True,
): ...
class Frozen(DefaultFrozenModel):
name: str
# TODO: no error here
# error: [unknown-argument]
f = Frozen(name="test")
# TODO: this should be an `invalid-assignment` error
f.name = "new"
class Mutable(DefaultFrozenModel, frozen=False):
name: str
# TODO: no error here
# error: [unknown-argument]
m = Mutable(name="test")
m.name = "new" # No error
```
## `field_specifiers`
The `field_specifiers` argument can be used to specify a list of functions that should be treated
similar to `dataclasses.field` for normal dataclasses.
The [`typing.dataclass_transform`] specification also allows classes (such as `dataclasses.Field`)
to be listed in `field_specifiers`, but it is currently unclear how this should work, and other type
checkers do not seem to support this either.
### Basic example
```py
from typing_extensions import dataclass_transform, Any
def fancy_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
@dataclass_transform(field_specifiers=(fancy_field,))
def fancy_model[T](cls: type[T]) -> type[T]:
...
return cls
@fancy_model
class Person:
id: int = fancy_field(init=False)
name: str = fancy_field()
age: int | None = fancy_field(kw_only=True)
# TODO: Should be `(self: Person, name: str, *, age: int | None) -> None`
reveal_type(Person.__init__) # revealed: (self: Person, id: int = Any, name: str = Any, age: int | None = Any) -> None
# TODO: No error here
# error: [invalid-argument-type]
alice = Person("Alice", age=30)
reveal_type(alice.id) # revealed: int
reveal_type(alice.name) # revealed: str
reveal_type(alice.age) # revealed: int | None
```
To do
## Overloaded dataclass-like decorators
@@ -577,32 +322,4 @@ D1(1.2) # error: [invalid-argument-type]
D2(1.2) # error: [invalid-argument-type]
```
### Use cases
#### Home Assistant
Home Assistant uses a pattern like this, where a `@dataclass`-decorated class inherits from a base
class that is itself a `dataclass`-like construct via a metaclass-based dataclass transformer. Make
sure that we recognize all fields in a hierarchy like this:
```py
from dataclasses import dataclass
from typing import dataclass_transform
@dataclass_transform()
class ModelMeta(type):
pass
class Sensor(metaclass=ModelMeta):
key: int
@dataclass(frozen=True, kw_only=True)
class TemperatureSensor(Sensor):
name: str
t = TemperatureSensor(key=1, name="Temperature Sensor")
reveal_type(t.key) # revealed: int
reveal_type(t.name) # revealed: str
```
[`typing.dataclass_transform`]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform

View File

@@ -1010,6 +1010,7 @@ python-version = "3.10"
```py
from dataclasses import dataclass, field, KW_ONLY
from typing_extensions import reveal_type
@dataclass
class C:
@@ -1204,9 +1205,9 @@ python-version = "3.12"
from dataclasses import dataclass
from typing import Callable
from types import FunctionType
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to, is_equivalent_to
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to
@dataclass(order=True)
@dataclass
class C:
x: int
@@ -1233,20 +1234,8 @@ static_assert(not is_assignable_to(EquivalentPureCallableType, DunderInitType))
static_assert(is_subtype_of(DunderInitType, EquivalentFunctionLikeCallableType))
static_assert(is_assignable_to(DunderInitType, EquivalentFunctionLikeCallableType))
static_assert(is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(is_equivalent_to(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(not is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(not is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(is_subtype_of(DunderInitType, FunctionType))
```
It should be possible to mock out synthesized methods:
```py
from unittest.mock import Mock
def test_c():
c = C(1)
c.__lt__ = Mock()
```

View File

@@ -1,139 +0,0 @@
# Legacy typevar creation diagnostics
The full tests for these features are in `generics/legacy/variables.md`.
<!-- snapshot-diagnostics -->
## Must have a name
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar()
```
## Name can't be given more than once
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", name="T")
```
## Must be directly assigned to a variable
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
> part of a larger expression).
```py
from typing import TypeVar
T = TypeVar("T")
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable]
tuple_with_typevar = ("foo", TypeVar("W"))
```
## `TypeVar` parameter must match variable name
> The argument to `TypeVar()` must be a string equal to the variable name to which it is assigned.
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("Q")
```
## No variadic arguments
```py
from typing import TypeVar
types = (int, str)
# error: [invalid-legacy-type-variable]
T = TypeVar("T", *types)
# error: [invalid-legacy-type-variable]
S = TypeVar("S", **{"bound": int})
```
## Cannot have only one constraint
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
> be at least two constraints, if any; specifying a single constraint is disallowed.
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int)
```
## Cannot have both bound and constraint
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int, str, bound=bytes)
```
## Cannot be both covariant and contravariant
> To facilitate the declaration of container types where covariant or contravariant type checking is
> acceptable, type variables accept keyword arguments `covariant=True` or `contravariant=True`. At
> most one of these may be passed.
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=True, contravariant=True)
```
## Boolean parameters must be unambiguous
```py
from typing_extensions import TypeVar
def cond() -> bool:
return True
# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=cond())
# error: [invalid-legacy-type-variable]
U = TypeVar("U", contravariant=cond())
# error: [invalid-legacy-type-variable]
V = TypeVar("V", infer_variance=cond())
```
## Invalid keyword arguments
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
## Invalid feature for this Python version
```toml
[environment]
python-version = "3.10"
```
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", default=int)
```

View File

@@ -61,35 +61,6 @@ class DataFrame:
pass
```
## Class from different module with the same qualified name
`package/__init__.py`:
```py
from .foo import MyClass
def make_MyClass() -> MyClass:
return MyClass()
```
`package/foo.pyi`:
```pyi
class MyClass: ...
```
`package/foo.py`:
```py
class MyClass: ...
def get_MyClass() -> MyClass:
from . import make_MyClass
# error: [invalid-return-type] "Return type does not match returned value: expected `package.foo.MyClass @ src/package/foo.py:1`, found `package.foo.MyClass @ src/package/foo.pyi:1`"
return make_MyClass()
```
## Enum from different modules
```py
@@ -234,8 +205,8 @@ from typing import Protocol
import proto_a
import proto_b
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
def _(drawable_b: proto_b.Drawable):
# error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
drawable: proto_a.Drawable = drawable_b
```

View File

@@ -3,6 +3,8 @@
## Invalid syntax
```py
from typing_extensions import reveal_type
try:
print
except as e: # error: [invalid-syntax]

View File

@@ -108,7 +108,7 @@ reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTyp
The type parameter can be specified explicitly:
```py
from typing_extensions import Generic, Literal, TypeVar
from typing import Generic, Literal, TypeVar
T = TypeVar("T")
@@ -195,7 +195,7 @@ reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
We can infer the type parameter from a type context:
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -240,7 +240,7 @@ consistent with each other.
### `__new__` only
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -257,7 +257,7 @@ wrong_innards: C[int] = C("five")
### `__init__` only
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -273,7 +273,7 @@ wrong_innards: C[int] = C("five")
### Identical `__new__` and `__init__` signatures
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -292,7 +292,7 @@ wrong_innards: C[int] = C("five")
### Compatible `__new__` and `__init__` signatures
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -325,7 +325,7 @@ If either method comes from a generic base class, we don't currently use its inf
to specialize the class.
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -344,7 +344,7 @@ reveal_type(D(1)) # revealed: D[int]
### Generic class inherits `__init__` from generic base class
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -364,7 +364,7 @@ reveal_type(D(1, "str")) # revealed: D[int, str]
This is a specific example of the above, since it was reported specifically by a user.
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -382,7 +382,7 @@ for `tuple`, so we use a different mechanism to make sure it has the right inher
context. But from the user's point of view, this is another example of the above.)
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -403,7 +403,7 @@ python-version = "3.11"
```
```py
from typing_extensions import TypeVar, Sequence, Never
from typing import TypeVar, Sequence, Never
T = TypeVar("T")
@@ -421,7 +421,7 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t
### `__init__` is itself generic
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
S = TypeVar("S")
T = TypeVar("T")
@@ -440,7 +440,7 @@ wrong_innards: C[int] = C("five", 1)
### Some `__init__` overloads only apply to certain specializations
```py
from typing_extensions import overload, Generic, TypeVar
from typing import overload, Generic, TypeVar
T = TypeVar("T")
@@ -480,7 +480,7 @@ C[None](12)
```py
from dataclasses import dataclass
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -494,7 +494,7 @@ reveal_type(A(x=1)) # revealed: A[int]
### Class typevar has another typevar as a default
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U", default=T)
@@ -515,7 +515,7 @@ When a generic subclass fills its superclass's type parameter with one of its ow
propagate through:
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -549,7 +549,7 @@ scope for the method.
```py
from ty_extensions import generic_context
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -581,7 +581,7 @@ In a specialized generic alias, the specialization is applied to the attributes
class.
```py
from typing_extensions import Generic, TypeVar, Protocol
from typing import Generic, TypeVar, Protocol
T = TypeVar("T")
U = TypeVar("U")
@@ -639,7 +639,7 @@ reveal_type(d.method3().x) # revealed: int
When a method is overloaded, the specialization is applied to all overloads.
```py
from typing_extensions import overload, Generic, TypeVar
from typing import overload, Generic, TypeVar
S = TypeVar("S")
@@ -667,7 +667,7 @@ A class can use itself as the type parameter of one of its superclasses. (This i
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
```pyi
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -682,7 +682,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
A similar case can work in a non-stub file, if forward references are stringified:
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -697,7 +697,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
In a non-stub file, without stringified forward references, this raises a `NameError`:
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -710,7 +710,7 @@ class Sub(Base[Sub]): ...
### Cyclic inheritance as a generic parameter
```pyi
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")
@@ -722,7 +722,7 @@ class Derived(list[Derived[T]], Generic[T]): ...
Inheritance that would result in a cyclic MRO is detected as an error.
```py
from typing_extensions import Generic, TypeVar
from typing import Generic, TypeVar
T = TypeVar("T")

View File

@@ -181,6 +181,7 @@ reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Literal[42, 43]
```py
from typing import TypeVar
from typing_extensions import reveal_type
T = TypeVar("T", bound=int)
@@ -199,6 +200,7 @@ reveal_type(f("string")) # revealed: Unknown
```py
from typing import TypeVar
from typing_extensions import reveal_type
T = TypeVar("T", int, None)
@@ -321,9 +323,6 @@ def union_param(x: T | None) -> T:
reveal_type(union_param("a")) # revealed: Literal["a"]
reveal_type(union_param(1)) # revealed: Literal[1]
reveal_type(union_param(None)) # revealed: Unknown
def _(x: int | None):
reveal_type(union_param(x)) # revealed: int
```
```py

View File

@@ -6,8 +6,6 @@ for both type variable syntaxes.
Unless otherwise specified, all quotations come from the [Generics] section of the typing spec.
Diagnostics for invalid type variables are snapshotted in `diagnostics/legacy_typevars.md`.
## Type variables
### Defining legacy type variables
@@ -26,16 +24,7 @@ reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```
The typevar name can also be provided as a keyword argument:
```py
from typing import TypeVar
T = TypeVar(name="T")
reveal_type(T.__name__) # revealed: Literal["T"]
```
### Must be directly assigned to a variable
### Directly assigned to a variable
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
> part of a larger expression).
@@ -44,24 +33,13 @@ reveal_type(T.__name__) # revealed: Literal["T"]
from typing import TypeVar
T = TypeVar("T")
# TODO: no error
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable]
tuple_with_typevar = ("foo", TypeVar("W"))
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
```
```py
from typing_extensions import TypeVar
T = TypeVar("T")
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable]
tuple_with_typevar = ("foo", TypeVar("W"))
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable"
# error: [invalid-type-form] "Function calls are not allowed in type expressions"
TestList = list[TypeVar("W")]
```
### `TypeVar` parameter must match variable name
@@ -71,7 +49,7 @@ reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
# error: [invalid-legacy-type-variable] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)"
T = TypeVar("Q")
```
@@ -88,22 +66,6 @@ T = TypeVar("T")
T = TypeVar("T")
```
### No variadic arguments
```py
from typing import TypeVar
types = (int, str)
# error: [invalid-legacy-type-variable]
T = TypeVar("T", *types)
reveal_type(T) # revealed: TypeVar
# error: [invalid-legacy-type-variable]
S = TypeVar("S", **{"bound": int})
reveal_type(S) # revealed: TypeVar
```
### Type variables with a default
Note that the `__default__` property is only available in Python ≥3.13.
@@ -129,11 +91,6 @@ reveal_type(S.__default__) # revealed: NoDefault
### Using other typevars as a default
```toml
[environment]
python-version = "3.13"
```
```py
from typing import Generic, TypeVar, Union
@@ -167,15 +124,6 @@ S = TypeVar("S")
reveal_type(S.__bound__) # revealed: None
```
The upper bound must be a valid type expression:
```py
from typing import TypedDict
# error: [invalid-type-form]
T = TypeVar("T", bound=TypedDict)
```
### Type variables with constraints
```py
@@ -190,16 +138,6 @@ S = TypeVar("S")
reveal_type(S.__constraints__) # revealed: tuple[()]
```
Constraints are not simplified relative to each other, even if one is a subtype of the other:
```py
T = TypeVar("T", int, bool)
reveal_type(T.__constraints__) # revealed: tuple[int, bool]
S = TypeVar("S", float, str)
reveal_type(S.__constraints__) # revealed: tuple[int | float, str]
```
### Cannot have only one constraint
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
@@ -208,19 +146,10 @@ reveal_type(S.__constraints__) # revealed: tuple[int | float, str]
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
# TODO: error: [invalid-type-variable-constraints]
T = TypeVar("T", int)
```
### Cannot have both bound and constraint
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int, str, bound=bytes)
```
### Cannot be both covariant and contravariant
> To facilitate the declaration of container types where covariant or contravariant type checking is
@@ -234,10 +163,10 @@ from typing import TypeVar
T = TypeVar("T", covariant=True, contravariant=True)
```
### Boolean parameters must be unambiguous
### Variance parameters must be unambiguous
```py
from typing_extensions import TypeVar
from typing import TypeVar
def cond() -> bool:
return True
@@ -247,73 +176,6 @@ T = TypeVar("T", covariant=cond())
# error: [invalid-legacy-type-variable]
U = TypeVar("U", contravariant=cond())
# error: [invalid-legacy-type-variable]
V = TypeVar("V", infer_variance=cond())
```
### Invalid keyword arguments
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
```pyi
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
### Constructor signature versioning
#### For `typing.TypeVar`
```toml
[environment]
python-version = "3.10"
```
In a stub file, features from the latest supported Python version can be used on any version.
There's no need to require use of `typing_extensions.TypeVar` in a stub file, when the type checker
can understand the typevar definition perfectly well either way, and there can be no runtime error.
(Perhaps it's arguable whether this special case is worth it, but other type checkers do it, so we
maintain compatibility.)
```pyi
from typing import TypeVar
T = TypeVar("T", default=int)
```
But this raises an error in a non-stub file:
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", default=int)
```
#### For `typing_extensions.TypeVar`
`typing_extensions.TypeVar` always supports the latest features, on any Python version.
```toml
[environment]
python-version = "3.10"
```
```py
from typing_extensions import TypeVar
T = TypeVar("T", default=int)
# TODO: should not error, should reveal `int`
# error: [unresolved-attribute]
reveal_type(T.__default__) # revealed: Unknown
```
## Callability
@@ -369,96 +231,4 @@ def constrained(x: T_constrained):
reveal_type(type(x)) # revealed: type[int] | type[str]
```
## Cycles
### Bounds and constraints
A typevar's bounds and constraints cannot be generic, cyclic or otherwise:
```py
from typing import Any, TypeVar
S = TypeVar("S")
# TODO: error
T = TypeVar("T", bound=list[S])
# TODO: error
U = TypeVar("U", list["T"], str)
# TODO: error
V = TypeVar("V", list["V"], str)
```
However, they are lazily evaluated and can cyclically refer to their own type:
```py
from typing import TypeVar, Generic
T = TypeVar("T", bound=list["G"])
class G(Generic[T]):
x: T
reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
```
### Defaults
```toml
[environment]
python-version = "3.13"
```
Defaults can be generic, but can only refer to typevars from the same scope if they were defined
earlier in that scope:
```py
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U", default=T)
class C(Generic[T, U]):
x: T
y: U
reveal_type(C[int, str]().x) # revealed: int
reveal_type(C[int, str]().y) # revealed: str
reveal_type(C[int]().x) # revealed: int
reveal_type(C[int]().y) # revealed: int
# TODO: error
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
```
## Regression
### Use of typevar with default inside a function body that binds it
```toml
[environment]
python-version = "3.13"
```
```py
from typing import Generic, TypeVar
_DataT = TypeVar("_DataT", bound=int, default=int)
class Event(Generic[_DataT]):
def __init__(self, data: _DataT) -> None:
self.data = data
def async_fire_internal(event_data: _DataT):
event: Event[_DataT] | None = None
event = Event(event_data)
```
[generics]: https://typing.python.org/en/latest/spec/generics.html

View File

@@ -286,9 +286,6 @@ def union_param[T](x: T | None) -> T:
reveal_type(union_param("a")) # revealed: Literal["a"]
reveal_type(union_param(1)) # revealed: Literal[1]
reveal_type(union_param(None)) # revealed: Unknown
def _(x: int | None):
reveal_type(union_param(x)) # revealed: int
```
```py

View File

@@ -0,0 +1,140 @@
# Hover type assertions
You can use the `hover` assertion to test the inferred type of an expression. This exercises the
same logic as the hover LSP action.
Typically, you will not need to use the `hover` action to test the behavior of our type inference
code, since you can also use `reveal_type` to display the inferred type of an expression. Since
`reveal_type` is part of the standard library, we prefer to use it when possible.
However, there are certain situations where `reveal_type` and `hover` will give different results.
In particular, `reveal_type` is not transparent to bidirectional type checking, as seen in the
"Different results" section below.
## Syntax
### Basic syntax
The `hover` assertion operates on a specific location in the source text. We find the "inner-most"
expression at that position, and then query the inferred type of that expression. The row to query
is identified just like any other mdtest assertion. The column to query is identified by a down
arrow (↓) in the assertion. (Note that the down arrow should always appear immediately before the
`hover` keyword in the assertion.)
```py
def test_basic_types(parameter: int) -> None:
# ↓ hover: int
parameter
# ↓ hover: Literal[10]
number = 10
# ↓ hover: Literal["hello"]
text = "hello"
```
### Multiple hovers on the same line
We can have multiple hover assertions for different positions on the same line:
```py
# ↓ hover: Literal[1]
# ↓ hover: Literal[2]
# ↓ hover: Literal[3]
total = 1 + 2 + 3
# ↓ hover: Literal[5]
# ↓ hover: Literal[3]
result = max(5, 3)
```
### Hovering works on every character in an expression
```py
def _(param: bool) -> None:
# ↓ hover: bool
# ↓ hover: bool
# ↓ hover: bool
# ↓ hover: bool
# ↓ hover: bool
result = param
```
### Hovering with unicode characters
```py
def _(café: str) -> None:
# ↓ hover: str
# ↓ hover: str
# ↓ hover: str
# ↓ hover: str
result = café
```
## Different results for `reveal_type` and `hover`
```py
from typing import overload
def f(x: dict[str, int]) -> None: ...
# revealed: dict[Unknown, Unknown]
f(reveal_type({}))
# ↓ hover: dict[str, int]
f({})
```
## Hovering on different expression types
### Literals
```py
# ↓ hover: Literal[42]
int_value = 42
# ↓ hover: Literal["test"]
string_value = "test"
# ↓ hover: Literal[True]
bool_value = True
```
### Names and attributes
```py
class MyClass:
value: int
def test_attributes(instance: MyClass) -> None:
# ↓ hover: MyClass
instance
# ↓ hover: int
instance.value
```
### Function definitions
```py
def f(x: int) -> None: ...
# ↓ hover: def f(x: int) -> None
result = f
```
### Binary operations
```py
# ↓ hover: Literal[10]
# ↓ hover: Literal[20]
result = 10 + 20
```
### Comprehensions
```py
# List comprehension
# ↓ hover: list[@Todo(list comprehension element type)]
result = [x for x in range(5)]
```

View File

@@ -5,6 +5,8 @@ all members available on a given type. This routine is used for autocomplete sug
## Basic functionality
<!-- snapshot-diagnostics -->
The `ty_extensions.all_members` and `ty_extensions.has_member` functions expose a Python-level API
that can be used to query which attributes `ide_support::all_members` understands as being available
on a given object. For example, all member functions of `str` are available on `"a"`. The Python API
@@ -43,10 +45,10 @@ The full list of all members is relatively long, but `reveal_type` can be used i
`all_members` to see them all:
```py
from typing_extensions import reveal_type
from ty_extensions import all_members
# revealed: tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]
reveal_type(all_members("a"))
reveal_type(all_members("a")) # error: [revealed-type]
```
## Kinds of types

View File

@@ -42,6 +42,8 @@ async def foo():
### No `__aiter__` method
```py
from typing_extensions import reveal_type
class NotAsyncIterable: ...
async def foo():
@@ -53,6 +55,8 @@ async def foo():
### Synchronously iterable, but not asynchronously iterable
```py
from typing_extensions import reveal_type
async def foo():
class Iterator:
def __next__(self) -> int:
@@ -70,6 +74,8 @@ async def foo():
### No `__anext__` method
```py
from typing_extensions import reveal_type
class NoAnext: ...
class AsyncIterable:
@@ -85,6 +91,8 @@ async def foo():
### Possibly missing `__anext__` method
```py
from typing_extensions import reveal_type
async def foo(flag: bool):
class PossiblyUnboundAnext:
if flag:
@@ -103,6 +111,8 @@ async def foo(flag: bool):
### Possibly missing `__aiter__` method
```py
from typing_extensions import reveal_type
async def foo(flag: bool):
class AsyncIterable:
async def __anext__(self) -> int:
@@ -121,6 +131,8 @@ async def foo(flag: bool):
### Wrong signature for `__aiter__`
```py
from typing_extensions import reveal_type
class AsyncIterator:
async def __anext__(self) -> int:
return 42
@@ -138,6 +150,8 @@ async def foo():
### Wrong signature for `__anext__`
```py
from typing_extensions import reveal_type
class AsyncIterator:
async def __anext__(self, arg: int) -> int: # wrong
return 42

View File

@@ -108,6 +108,8 @@ reveal_type(x)
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class NotIterable:
if flag:
@@ -245,7 +247,8 @@ class StrIterator:
def f(x: IntIterator | StrIterator):
for a in x:
reveal_type(a) # revealed: int | str
# TODO: this should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(a) # revealed: int
```
Most real-world iterable types use `Iterator` as the return annotation of their `__iter__` methods:
@@ -257,11 +260,14 @@ def g(
c: Literal["foo", b"bar"],
):
for x in a:
reveal_type(x) # revealed: int | str
# TODO: should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(x) # revealed: int
for y in b:
reveal_type(y) # revealed: str | int
# TODO: should be `str | int` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(y) # revealed: str
for z in c:
reveal_type(z) # revealed: LiteralString | int
# TODO: should be `LiteralString | int` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(z) # revealed: LiteralString
```
## Union type as iterable where one union element has no `__iter__` method
@@ -269,6 +275,8 @@ def g(
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class TestIter:
def __next__(self) -> int:
return 42
@@ -288,6 +296,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class TestIter:
def __next__(self) -> int:
return 42
@@ -364,6 +374,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> int:
return 42
@@ -382,6 +394,8 @@ for x in Iterable():
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Bad:
def __iter__(self) -> int:
return 42
@@ -414,6 +428,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator1:
def __next__(self, extra_arg) -> int:
return 42
@@ -443,6 +459,8 @@ for y in Iterable2():
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class Iterator:
def __next__(self) -> int:
@@ -495,6 +513,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> int:
return 42
@@ -518,6 +538,8 @@ def _(flag1: bool, flag2: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Bad:
__getitem__: None = None
@@ -531,6 +553,8 @@ for x in Bad():
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class CustomCallable:
if flag:
@@ -565,6 +589,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterable:
# invalid because it will implicitly be passed an `int`
# by the interpreter
@@ -604,6 +630,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> int:
return 42
@@ -639,6 +667,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class Iterator1:
if flag:
@@ -678,6 +708,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class Iterable1:
if flag:
@@ -709,6 +741,8 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> bytes:
return b"foo"

View File

@@ -241,6 +241,8 @@ find a union type in a class's bases, we infer the class's `__mro__` as being
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
```py
from typing_extensions import reveal_type
def returns_bool() -> bool:
return True
@@ -389,6 +391,8 @@ class BadSub2(Bad2()): ... # error: [invalid-base]
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]

View File

@@ -281,7 +281,7 @@ def _(x: Foo | Bar, flag: bool) -> None:
The `TypeIs` type remains effective across generic boundaries:
```py
from typing_extensions import TypeVar
from typing_extensions import TypeVar, reveal_type
T = TypeVar("T")

View File

@@ -197,9 +197,9 @@ from typing_extensions import TypeAliasType, TypeVar
T = TypeVar("T")
IntAndT = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
def f(x: IntAndT[str]) -> None:
def f(x: IntAnd[str]) -> None:
reveal_type(x) # revealed: @Todo(Generic manual PEP-695 type alias)
```

View File

@@ -347,7 +347,7 @@ python-version = "3.12"
```
```py
from typing_extensions import Protocol
from typing_extensions import Protocol, reveal_type
# error: [call-non-callable]
reveal_type(Protocol()) # revealed: Unknown
@@ -381,7 +381,9 @@ And as a corollary, `type[MyProtocol]` can also be called:
```py
def f(x: type[MyProtocol]):
reveal_type(x()) # revealed: @Todo(type[T] for protocols)
# TODO: add a `reveal_type` call here once it's no longer a `Todo` type
# (which doesn't work well with snapshots)
x()
```
## Members of a protocol
@@ -532,7 +534,7 @@ python-version = "3.9"
```py
import sys
from typing_extensions import Protocol, get_protocol_members
from typing_extensions import Protocol, get_protocol_members, reveal_type
class Foo(Protocol):
if sys.version_info >= (3, 10):
@@ -615,10 +617,11 @@ static_assert(is_assignable_to(Foo, HasX))
static_assert(not is_subtype_of(Foo, HasXY))
static_assert(not is_assignable_to(Foo, HasXY))
static_assert(not is_subtype_of(HasXIntSub, HasX))
static_assert(not is_assignable_to(HasXIntSub, HasX))
static_assert(not is_subtype_of(HasX, HasXIntSub))
static_assert(not is_assignable_to(HasX, HasXIntSub))
# TODO: these should pass
static_assert(not is_subtype_of(HasXIntSub, HasX)) # error: [static-assert-error]
static_assert(not is_assignable_to(HasXIntSub, HasX)) # error: [static-assert-error]
static_assert(not is_subtype_of(HasX, HasXIntSub)) # error: [static-assert-error]
static_assert(not is_assignable_to(HasX, HasXIntSub)) # error: [static-assert-error]
class FooSub(Foo): ...
@@ -2288,9 +2291,10 @@ class MethodPUnrelated(Protocol):
static_assert(is_subtype_of(MethodPSub, MethodPSuper))
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper))
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated))
static_assert(not is_assignable_to(MethodPSuper, MethodPSub))
# TODO: these should pass
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper)) # error: [static-assert-error]
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated)) # error: [static-assert-error]
static_assert(not is_assignable_to(MethodPSuper, MethodPSub)) # error: [static-assert-error]
```
## Subtyping between protocols with method members and protocols with non-method members
@@ -2349,7 +2353,8 @@ And for the same reason, they are never assignable to attribute members (which a
class Attribute(Protocol):
f: Callable[[], bool]
static_assert(not is_assignable_to(Method, Attribute))
# TODO: should pass
static_assert(not is_assignable_to(Method, Attribute)) # error: [static-assert-error]
```
Protocols with attribute members, meanwhile, cannot be assigned to protocols with method members,
@@ -2358,8 +2363,9 @@ this is not true for attribute members. The same principle also applies for prot
members
```py
static_assert(not is_assignable_to(PropertyBool, Method))
static_assert(not is_assignable_to(Attribute, Method))
# TODO: this should pass
static_assert(not is_assignable_to(PropertyBool, Method)) # error: [static-assert-error]
static_assert(not is_assignable_to(Attribute, Method)) # error: [static-assert-error]
```
But an exception to this rule is if an attribute member is marked as `ClassVar`, as this guarantees
@@ -2378,8 +2384,9 @@ static_assert(is_assignable_to(ClassVarAttribute, Method))
class ClassVarAttributeBad(Protocol):
f: ClassVar[Callable[[], str]]
static_assert(not is_subtype_of(ClassVarAttributeBad, Method))
static_assert(not is_assignable_to(ClassVarAttributeBad, Method))
# TODO: these should pass:
static_assert(not is_subtype_of(ClassVarAttributeBad, Method)) # error: [static-assert-error]
static_assert(not is_assignable_to(ClassVarAttributeBad, Method)) # error: [static-assert-error]
```
## Narrowing of protocols
@@ -2391,7 +2398,7 @@ By default, a protocol class cannot be used as the second argument to `isinstanc
type inside these branches (this matches the behavior of other type checkers):
```py
from typing_extensions import Protocol
from typing_extensions import Protocol, reveal_type
class HasX(Protocol):
x: int
@@ -2700,8 +2707,9 @@ class RecursiveNonFullyStatic(Protocol):
parent: RecursiveNonFullyStatic
x: Any
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
# TODO: these should pass, once we take into account types of members
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic)) # error: [static-assert-error]
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic)) # error: [static-assert-error]
static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveNonFullyStatic))
static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
@@ -2719,7 +2727,9 @@ class RecursiveOptionalParent(Protocol):
static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
# Due to invariance of mutable attribute members, neither is assignable to the other
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
#
# TODO: should pass
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
class Other(Protocol):

View File

@@ -339,7 +339,7 @@ class A: ...
def f(x: A):
# TODO: no error
# error: [invalid-assignment] "Object of type `mdtest_snippet.A @ src/mdtest_snippet.py:12 | mdtest_snippet.A @ src/mdtest_snippet.py:13` is not assignable to `mdtest_snippet.A @ src/mdtest_snippet.py:13`"
# error: [invalid-assignment] "Object of type `mdtest_snippet.A | mdtest_snippet.A` is not assignable to `mdtest_snippet.A`"
x = A()
```

View File

@@ -0,0 +1,43 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: all_members.md - List all members - Basic functionality
mdtest path: crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md
---
# Python source files
## mdtest_snippet.py
```
1 | from ty_extensions import static_assert, has_member
2 |
3 | static_assert(has_member("a", "replace"))
4 | static_assert(has_member("a", "startswith"))
5 | static_assert(has_member("a", "isupper"))
6 | static_assert(has_member("a", "__add__"))
7 | static_assert(has_member("a", "__gt__"))
8 | static_assert(has_member("a", "__doc__"))
9 | static_assert(has_member("a", "__repr__"))
10 | static_assert(not has_member("a", "non_existent"))
11 | from typing_extensions import reveal_type
12 | from ty_extensions import all_members
13 |
14 | reveal_type(all_members("a")) # error: [revealed-type]
```
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:13
|
12 | from ty_extensions import all_members
13 |
14 | reveal_type(all_members("a")) # error: [revealed-type]
| ^^^^^^^^^^^^^^^^ `tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]`
|
```

View File

@@ -12,27 +12,41 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | class NotAsyncIterable: ...
1 | from typing_extensions import reveal_type
2 |
3 | async def foo():
4 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
5 | async for x in NotAsyncIterable():
6 | reveal_type(x) # revealed: Unknown
3 | class NotAsyncIterable: ...
4 |
5 | async def foo():
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
7 | async for x in NotAsyncIterable():
8 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `NotAsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:5:20
--> src/mdtest_snippet.py:7:20
|
3 | async def foo():
4 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
5 | async for x in NotAsyncIterable():
5 | async def foo():
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
7 | async for x in NotAsyncIterable():
| ^^^^^^^^^^^^^^^^^^
6 | reveal_type(x) # revealed: Unknown
8 | reveal_type(x) # revealed: Unknown
|
info: It has no `__aiter__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:21
|
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
7 | async for x in NotAsyncIterable():
8 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,31 +12,45 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | class NoAnext: ...
1 | from typing_extensions import reveal_type
2 |
3 | class AsyncIterable:
4 | def __aiter__(self) -> NoAnext:
5 | return NoAnext()
6 |
7 | async def foo():
8 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
9 | async for x in AsyncIterable():
10 | reveal_type(x) # revealed: Unknown
3 | class NoAnext: ...
4 |
5 | class AsyncIterable:
6 | def __aiter__(self) -> NoAnext:
7 | return NoAnext()
8 |
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:9:20
--> src/mdtest_snippet.py:11:20
|
7 | async def foo():
8 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
9 | async for x in AsyncIterable():
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
10 | reveal_type(x) # revealed: Unknown
12 | reveal_type(x) # revealed: Unknown
|
info: Its `__aiter__` method returns an object of type `NoAnext`, which has no `__anext__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:12:21
|
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,33 +12,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | async def foo(flag: bool):
2 | class AsyncIterable:
3 | async def __anext__(self) -> int:
4 | return 42
5 |
6 | class PossiblyUnboundAiter:
7 | if flag:
8 | def __aiter__(self) -> AsyncIterable:
9 | return AsyncIterable()
10 |
11 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
12 | async for x in PossiblyUnboundAiter():
13 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | async def foo(flag: bool):
4 | class AsyncIterable:
5 | async def __anext__(self) -> int:
6 | return 42
7 |
8 | class PossiblyUnboundAiter:
9 | if flag:
10 | def __aiter__(self) -> AsyncIterable:
11 | return AsyncIterable()
12 |
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
14 | async for x in PossiblyUnboundAiter():
15 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `PossiblyUnboundAiter` may not be async-iterable
--> src/mdtest_snippet.py:12:20
--> src/mdtest_snippet.py:14:20
|
11 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
12 | async for x in PossiblyUnboundAiter():
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
14 | async for x in PossiblyUnboundAiter():
| ^^^^^^^^^^^^^^^^^^^^^^
13 | reveal_type(x) # revealed: int
15 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` attribute (with type `bound method PossiblyUnboundAiter.__aiter__() -> AsyncIterable`) may not be callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:15:21
|
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
14 | async for x in PossiblyUnboundAiter():
15 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,33 +12,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | async def foo(flag: bool):
2 | class PossiblyUnboundAnext:
3 | if flag:
4 | async def __anext__(self) -> int:
5 | return 42
6 |
7 | class AsyncIterable:
8 | def __aiter__(self) -> PossiblyUnboundAnext:
9 | return PossiblyUnboundAnext()
10 |
11 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
12 | async for x in AsyncIterable():
13 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | async def foo(flag: bool):
4 | class PossiblyUnboundAnext:
5 | if flag:
6 | async def __anext__(self) -> int:
7 | return 42
8 |
9 | class AsyncIterable:
10 | def __aiter__(self) -> PossiblyUnboundAnext:
11 | return PossiblyUnboundAnext()
12 |
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
14 | async for x in AsyncIterable():
15 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` may not be async-iterable
--> src/mdtest_snippet.py:12:20
--> src/mdtest_snippet.py:14:20
|
11 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
12 | async for x in AsyncIterable():
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
14 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
13 | reveal_type(x) # revealed: int
15 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` method returns an object of type `PossiblyUnboundAnext`, which may not have a `__anext__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:15:21
|
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
14 | async for x in AsyncIterable():
15 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,32 +12,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | async def foo():
2 | class Iterator:
3 | def __next__(self) -> int:
4 | return 42
5 |
6 | class Iterable:
7 | def __iter__(self) -> Iterator:
8 | return Iterator()
9 |
10 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
11 | async for x in Iterator():
12 | reveal_type(x) # revealed: Unknown
1 | from typing_extensions import reveal_type
2 |
3 | async def foo():
4 | class Iterator:
5 | def __next__(self) -> int:
6 | return 42
7 |
8 | class Iterable:
9 | def __iter__(self) -> Iterator:
10 | return Iterator()
11 |
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
13 | async for x in Iterator():
14 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterator` is not async-iterable
--> src/mdtest_snippet.py:11:20
--> src/mdtest_snippet.py:13:20
|
10 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
11 | async for x in Iterator():
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
13 | async for x in Iterator():
| ^^^^^^^^^^
12 | reveal_type(x) # revealed: Unknown
14 | reveal_type(x) # revealed: Unknown
|
info: It has no `__aiter__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
13 | async for x in Iterator():
14 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,34 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | class AsyncIterator:
2 | async def __anext__(self, arg: int) -> int: # wrong
3 | return 42
4 |
5 | class AsyncIterable:
6 | def __aiter__(self) -> AsyncIterator:
7 | return AsyncIterator()
8 |
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | class AsyncIterator:
4 | async def __anext__(self, arg: int) -> int: # wrong
5 | return 42
6 |
7 | class AsyncIterable:
8 | def __aiter__(self) -> AsyncIterator:
9 | return AsyncIterator()
10 |
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:11:20
--> src/mdtest_snippet.py:13:20
|
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
12 | reveal_type(x) # revealed: int
14 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` method returns an object of type `AsyncIterator`, which has an invalid `__anext__` method
info: Expected signature for `__anext__` is `def __anext__(self): ...`
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,34 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | class AsyncIterator:
2 | async def __anext__(self) -> int:
3 | return 42
4 |
5 | class AsyncIterable:
6 | def __aiter__(self, arg: int) -> AsyncIterator: # wrong
7 | return AsyncIterator()
8 |
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | class AsyncIterator:
4 | async def __anext__(self) -> int:
5 | return 42
6 |
7 | class AsyncIterable:
8 | def __aiter__(self, arg: int) -> AsyncIterator: # wrong
9 | return AsyncIterator()
10 |
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:11:20
--> src/mdtest_snippet.py:13:20
|
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
12 | reveal_type(x) # revealed: int
14 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` method has an invalid signature
info: Expected signature `def __aiter__(self): ...`
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -13,71 +13,86 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.
```
1 | from dataclasses import dataclass, field, KW_ONLY
2 |
3 | @dataclass
4 | class C:
5 | x: int
6 | _: KW_ONLY
7 | y: str
8 |
9 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
10 |
11 | # error: [missing-argument]
12 | # error: [too-many-positional-arguments]
13 | C(3, "")
14 |
15 | C(3, y="")
16 | @dataclass
17 | class Fails: # error: [duplicate-kw-only]
18 | a: int
19 | b: KW_ONLY
20 | c: str
21 | d: KW_ONLY
22 | e: bytes
23 |
24 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
25 | def flag() -> bool:
26 | return True
27 |
28 | @dataclass
29 | class D: # error: [duplicate-kw-only]
30 | x: int
31 | _1: KW_ONLY
32 |
33 | if flag():
34 | y: str
35 | _2: KW_ONLY
36 | z: float
37 | from dataclasses import dataclass, KW_ONLY
38 |
39 | @dataclass
40 | class D:
41 | x: int
42 | _: KW_ONLY
43 | y: str
44 |
45 | @dataclass
46 | class E(D):
47 | z: bytes
48 |
49 | # This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
50 | E(1, b"foo", y="foo")
51 |
52 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
2 | from typing_extensions import reveal_type
3 |
4 | @dataclass
5 | class C:
6 | x: int
7 | _: KW_ONLY
8 | y: str
9 |
10 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
11 |
12 | # error: [missing-argument]
13 | # error: [too-many-positional-arguments]
14 | C(3, "")
15 |
16 | C(3, y="")
17 | @dataclass
18 | class Fails: # error: [duplicate-kw-only]
19 | a: int
20 | b: KW_ONLY
21 | c: str
22 | d: KW_ONLY
23 | e: bytes
24 |
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
26 | def flag() -> bool:
27 | return True
28 |
29 | @dataclass
30 | class D: # error: [duplicate-kw-only]
31 | x: int
32 | _1: KW_ONLY
33 |
34 | if flag():
35 | y: str
36 | _2: KW_ONLY
37 | z: float
38 | from dataclasses import dataclass, KW_ONLY
39 |
40 | @dataclass
41 | class D:
42 | x: int
43 | _: KW_ONLY
44 | y: str
45 |
46 | @dataclass
47 | class E(D):
48 | z: bytes
49 |
50 | # This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
51 | E(1, b"foo", y="foo")
52 |
53 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
```
# Diagnostics
```
error[missing-argument]: No argument provided for required parameter `y`
--> src/mdtest_snippet.py:13:1
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
11 | # error: [missing-argument]
12 | # error: [too-many-positional-arguments]
13 | C(3, "")
8 | y: str
9 |
10 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
| ^^^^^^^^^^ `(self: C, x: int, *, y: str) -> None`
11 |
12 | # error: [missing-argument]
|
```
```
error[missing-argument]: No argument provided for required parameter `y`
--> src/mdtest_snippet.py:14:1
|
12 | # error: [missing-argument]
13 | # error: [too-many-positional-arguments]
14 | C(3, "")
| ^^^^^^^^
14 |
15 | C(3, y="")
15 |
16 | C(3, y="")
|
info: rule `missing-argument` is enabled by default
@@ -85,14 +100,14 @@ info: rule `missing-argument` is enabled by default
```
error[too-many-positional-arguments]: Too many positional arguments: expected 1, got 2
--> src/mdtest_snippet.py:13:6
--> src/mdtest_snippet.py:14:6
|
11 | # error: [missing-argument]
12 | # error: [too-many-positional-arguments]
13 | C(3, "")
12 | # error: [missing-argument]
13 | # error: [too-many-positional-arguments]
14 | C(3, "")
| ^^
14 |
15 | C(3, y="")
15 |
16 | C(3, y="")
|
info: rule `too-many-positional-arguments` is enabled by default
@@ -100,14 +115,14 @@ info: rule `too-many-positional-arguments` is enabled by default
```
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
--> src/mdtest_snippet.py:17:7
--> src/mdtest_snippet.py:18:7
|
15 | C(3, y="")
16 | @dataclass
17 | class Fails: # error: [duplicate-kw-only]
16 | C(3, y="")
17 | @dataclass
18 | class Fails: # error: [duplicate-kw-only]
| ^^^^^
18 | a: int
19 | b: KW_ONLY
19 | a: int
20 | b: KW_ONLY
|
info: `KW_ONLY` fields: `b`, `d`
info: rule `duplicate-kw-only` is enabled by default
@@ -115,16 +130,42 @@ info: rule `duplicate-kw-only` is enabled by default
```
```
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
--> src/mdtest_snippet.py:29:7
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:25:13
|
28 | @dataclass
29 | class D: # error: [duplicate-kw-only]
23 | e: bytes
24 |
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
| ^^^^^^^^^^^^^^ `(self: Fails, a: int, *, c: str, e: bytes) -> None`
26 | def flag() -> bool:
27 | return True
|
```
```
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
--> src/mdtest_snippet.py:30:7
|
29 | @dataclass
30 | class D: # error: [duplicate-kw-only]
| ^
30 | x: int
31 | _1: KW_ONLY
31 | x: int
32 | _1: KW_ONLY
|
info: `KW_ONLY` fields: `_1`, `_2`
info: rule `duplicate-kw-only` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:53:13
|
51 | E(1, b"foo", y="foo")
52 |
53 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
| ^^^^^^^^^^ `(self: E, x: int, z: bytes, *, y: str) -> None`
|
```

View File

@@ -12,30 +12,44 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Iterable:
2 | # invalid because it will implicitly be passed an `int`
3 | # by the interpreter
4 | def __getitem__(self, key: str) -> int:
5 | return 42
6 |
7 | # error: [not-iterable]
8 | for x in Iterable():
9 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | class Iterable:
4 | # invalid because it will implicitly be passed an `int`
5 | # by the interpreter
6 | def __getitem__(self, key: str) -> int:
7 | return 42
8 |
9 | # error: [not-iterable]
10 | for x in Iterable():
11 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` is not iterable
--> src/mdtest_snippet.py:8:10
|
7 | # error: [not-iterable]
8 | for x in Iterable():
| ^^^^^^^^^^
9 | reveal_type(x) # revealed: int
|
--> src/mdtest_snippet.py:10:10
|
9 | # error: [not-iterable]
10 | for x in Iterable():
| ^^^^^^^^^^
11 | reveal_type(x) # revealed: int
|
info: It has no `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:11:17
|
9 | # error: [not-iterable]
10 | for x in Iterable():
11 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,26 +12,40 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Bad:
2 | __getitem__: None = None
3 |
4 | # error: [not-iterable]
5 | for x in Bad():
6 | reveal_type(x) # revealed: Unknown
1 | from typing_extensions import reveal_type
2 |
3 | class Bad:
4 | __getitem__: None = None
5 |
6 | # error: [not-iterable]
7 | for x in Bad():
8 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Bad` is not iterable
--> src/mdtest_snippet.py:5:10
--> src/mdtest_snippet.py:7:10
|
4 | # error: [not-iterable]
5 | for x in Bad():
6 | # error: [not-iterable]
7 | for x in Bad():
| ^^^^^
6 | reveal_type(x) # revealed: Unknown
8 | reveal_type(x) # revealed: Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:17
|
6 | # error: [not-iterable]
7 | for x in Bad():
8 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,46 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | def _(flag: bool):
2 | class CustomCallable:
3 | if flag:
4 | def __call__(self, *args, **kwargs) -> int:
5 | return 42
6 | else:
7 | __call__: None = None
8 |
9 | class Iterable1:
10 | __getitem__: CustomCallable = CustomCallable()
11 |
12 | class Iterable2:
13 | if flag:
14 | def __getitem__(self, key: int) -> int:
15 | return 42
16 | else:
17 | __getitem__: None = None
18 |
19 | # error: [not-iterable]
20 | for x in Iterable1():
21 | # TODO... `int` might be ideal here?
22 | reveal_type(x) # revealed: int | Unknown
23 |
24 | # error: [not-iterable]
25 | for y in Iterable2():
26 | # TODO... `int` might be ideal here?
27 | reveal_type(y) # revealed: int | Unknown
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class CustomCallable:
5 | if flag:
6 | def __call__(self, *args, **kwargs) -> int:
7 | return 42
8 | else:
9 | __call__: None = None
10 |
11 | class Iterable1:
12 | __getitem__: CustomCallable = CustomCallable()
13 |
14 | class Iterable2:
15 | if flag:
16 | def __getitem__(self, key: int) -> int:
17 | return 42
18 | else:
19 | __getitem__: None = None
20 |
21 | # error: [not-iterable]
22 | for x in Iterable1():
23 | # TODO... `int` might be ideal here?
24 | reveal_type(x) # revealed: int | Unknown
25 |
26 | # error: [not-iterable]
27 | for y in Iterable2():
28 | # TODO... `int` might be ideal here?
29 | reveal_type(y) # revealed: int | Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:20:14
--> src/mdtest_snippet.py:22:14
|
19 | # error: [not-iterable]
20 | for x in Iterable1():
21 | # error: [not-iterable]
22 | for x in Iterable1():
| ^^^^^^^^^^^
21 | # TODO... `int` might be ideal here?
22 | reveal_type(x) # revealed: int | Unknown
23 | # TODO... `int` might be ideal here?
24 | reveal_type(x) # revealed: int | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `CustomCallable`, which is not callable
@@ -60,17 +62,43 @@ info: rule `not-iterable` is enabled by default
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:25:14
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:24:21
|
24 | # error: [not-iterable]
25 | for y in Iterable2():
22 | for x in Iterable1():
23 | # TODO... `int` might be ideal here?
24 | reveal_type(x) # revealed: int | Unknown
| ^ `int | Unknown`
25 |
26 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:27:14
|
26 | # error: [not-iterable]
27 | for y in Iterable2():
| ^^^^^^^^^^^
26 | # TODO... `int` might be ideal here?
27 | reveal_type(y) # revealed: int | Unknown
28 | # TODO... `int` might be ideal here?
29 | reveal_type(y) # revealed: int | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `(bound method Iterable2.__getitem__(key: int) -> int) | None`, which is not callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:29:21
|
27 | for y in Iterable2():
28 | # TODO... `int` might be ideal here?
29 | reveal_type(y) # revealed: int | Unknown
| ^ `int | Unknown`
|
```

View File

@@ -12,46 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Iterator:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | def _(flag: bool):
6 | class Iterable1:
7 | if flag:
8 | def __iter__(self) -> Iterator:
9 | return Iterator()
10 | else:
11 | def __iter__(self, invalid_extra_arg) -> Iterator:
12 | return Iterator()
13 |
14 | # error: [not-iterable]
15 | for x in Iterable1():
16 | reveal_type(x) # revealed: int
17 |
18 | class Iterable2:
19 | if flag:
20 | def __iter__(self) -> Iterator:
21 | return Iterator()
22 | else:
23 | __iter__: None = None
24 |
25 | # error: [not-iterable]
26 | for x in Iterable2():
27 | # TODO: `int` would probably be better here:
28 | reveal_type(x) # revealed: int | Unknown
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | def _(flag: bool):
8 | class Iterable1:
9 | if flag:
10 | def __iter__(self) -> Iterator:
11 | return Iterator()
12 | else:
13 | def __iter__(self, invalid_extra_arg) -> Iterator:
14 | return Iterator()
15 |
16 | # error: [not-iterable]
17 | for x in Iterable1():
18 | reveal_type(x) # revealed: int
19 |
20 | class Iterable2:
21 | if flag:
22 | def __iter__(self) -> Iterator:
23 | return Iterator()
24 | else:
25 | __iter__: None = None
26 |
27 | # error: [not-iterable]
28 | for x in Iterable2():
29 | # TODO: `int` would probably be better here:
30 | reveal_type(x) # revealed: int | Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:15:14
--> src/mdtest_snippet.py:17:14
|
14 | # error: [not-iterable]
15 | for x in Iterable1():
16 | # error: [not-iterable]
17 | for x in Iterable1():
| ^^^^^^^^^^^
16 | reveal_type(x) # revealed: int
18 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method may have an invalid signature
info: Type of `__iter__` is `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`
@@ -61,16 +63,42 @@ info: rule `not-iterable` is enabled by default
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:26:14
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:18:21
|
25 | # error: [not-iterable]
26 | for x in Iterable2():
16 | # error: [not-iterable]
17 | for x in Iterable1():
18 | reveal_type(x) # revealed: int
| ^ `int`
19 |
20 | class Iterable2:
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:28:14
|
27 | # error: [not-iterable]
28 | for x in Iterable2():
| ^^^^^^^^^^^
27 | # TODO: `int` would probably be better here:
28 | reveal_type(x) # revealed: int | Unknown
29 | # TODO: `int` would probably be better here:
30 | reveal_type(x) # revealed: int | Unknown
|
info: Its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:30:21
|
28 | for x in Iterable2():
29 | # TODO: `int` would probably be better here:
30 | reveal_type(x) # revealed: int | Unknown
| ^ `int | Unknown`
|
```

View File

@@ -12,43 +12,45 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | def _(flag: bool):
2 | class Iterable1:
3 | if flag:
4 | def __getitem__(self, item: int) -> str:
5 | return "foo"
6 | else:
7 | __getitem__: None = None
8 |
9 | class Iterable2:
10 | if flag:
11 | def __getitem__(self, item: int) -> str:
12 | return "foo"
13 | else:
14 | def __getitem__(self, item: str) -> int:
15 | return 42
16 |
17 | # error: [not-iterable]
18 | for x in Iterable1():
19 | # TODO: `str` might be better
20 | reveal_type(x) # revealed: str | Unknown
21 |
22 | # error: [not-iterable]
23 | for y in Iterable2():
24 | reveal_type(y) # revealed: str | int
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class Iterable1:
5 | if flag:
6 | def __getitem__(self, item: int) -> str:
7 | return "foo"
8 | else:
9 | __getitem__: None = None
10 |
11 | class Iterable2:
12 | if flag:
13 | def __getitem__(self, item: int) -> str:
14 | return "foo"
15 | else:
16 | def __getitem__(self, item: str) -> int:
17 | return 42
18 |
19 | # error: [not-iterable]
20 | for x in Iterable1():
21 | # TODO: `str` might be better
22 | reveal_type(x) # revealed: str | Unknown
23 |
24 | # error: [not-iterable]
25 | for y in Iterable2():
26 | reveal_type(y) # revealed: str | int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:18:14
--> src/mdtest_snippet.py:20:14
|
17 | # error: [not-iterable]
18 | for x in Iterable1():
19 | # error: [not-iterable]
20 | for x in Iterable1():
| ^^^^^^^^^^^
19 | # TODO: `str` might be better
20 | reveal_type(x) # revealed: str | Unknown
21 | # TODO: `str` might be better
22 | reveal_type(x) # revealed: str | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `(bound method Iterable1.__getitem__(item: int) -> str) | None`, which is not callable
@@ -57,16 +59,42 @@ info: rule `not-iterable` is enabled by default
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:23:14
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:22:21
|
22 | # error: [not-iterable]
23 | for y in Iterable2():
20 | for x in Iterable1():
21 | # TODO: `str` might be better
22 | reveal_type(x) # revealed: str | Unknown
| ^ `str | Unknown`
23 |
24 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:25:14
|
24 | # error: [not-iterable]
25 | for y in Iterable2():
| ^^^^^^^^^^^
24 | reveal_type(y) # revealed: str | int
26 | reveal_type(y) # revealed: str | int
|
info: It has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:26:21
|
24 | # error: [not-iterable]
25 | for y in Iterable2():
26 | reveal_type(y) # revealed: str | int
| ^ `str | int`
|
```

View File

@@ -12,50 +12,52 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | def _(flag: bool):
2 | class Iterator1:
3 | if flag:
4 | def __next__(self) -> int:
5 | return 42
6 | else:
7 | def __next__(self, invalid_extra_arg) -> str:
8 | return "foo"
9 |
10 | class Iterator2:
11 | if flag:
12 | def __next__(self) -> int:
13 | return 42
14 | else:
15 | __next__: None = None
16 |
17 | class Iterable1:
18 | def __iter__(self) -> Iterator1:
19 | return Iterator1()
20 |
21 | class Iterable2:
22 | def __iter__(self) -> Iterator2:
23 | return Iterator2()
24 |
25 | # error: [not-iterable]
26 | for x in Iterable1():
27 | reveal_type(x) # revealed: int | str
28 |
29 | # error: [not-iterable]
30 | for y in Iterable2():
31 | # TODO: `int` would probably be better here:
32 | reveal_type(y) # revealed: int | Unknown
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class Iterator1:
5 | if flag:
6 | def __next__(self) -> int:
7 | return 42
8 | else:
9 | def __next__(self, invalid_extra_arg) -> str:
10 | return "foo"
11 |
12 | class Iterator2:
13 | if flag:
14 | def __next__(self) -> int:
15 | return 42
16 | else:
17 | __next__: None = None
18 |
19 | class Iterable1:
20 | def __iter__(self) -> Iterator1:
21 | return Iterator1()
22 |
23 | class Iterable2:
24 | def __iter__(self) -> Iterator2:
25 | return Iterator2()
26 |
27 | # error: [not-iterable]
28 | for x in Iterable1():
29 | reveal_type(x) # revealed: int | str
30 |
31 | # error: [not-iterable]
32 | for y in Iterable2():
33 | # TODO: `int` would probably be better here:
34 | reveal_type(y) # revealed: int | Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:26:14
--> src/mdtest_snippet.py:28:14
|
25 | # error: [not-iterable]
26 | for x in Iterable1():
27 | # error: [not-iterable]
28 | for x in Iterable1():
| ^^^^^^^^^^^
27 | reveal_type(x) # revealed: int | str
29 | reveal_type(x) # revealed: int | str
|
info: Its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method
info: Expected signature for `__next__` is `def __next__(self): ...`
@@ -64,16 +66,42 @@ info: rule `not-iterable` is enabled by default
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:30:14
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:29:21
|
29 | # error: [not-iterable]
30 | for y in Iterable2():
27 | # error: [not-iterable]
28 | for x in Iterable1():
29 | reveal_type(x) # revealed: int | str
| ^ `int | str`
30 |
31 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:32:14
|
31 | # error: [not-iterable]
32 | for y in Iterable2():
| ^^^^^^^^^^^
31 | # TODO: `int` would probably be better here:
32 | reveal_type(y) # revealed: int | Unknown
33 | # TODO: `int` would probably be better here:
34 | reveal_type(y) # revealed: int | Unknown
|
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:34:21
|
32 | for y in Iterable2():
33 | # TODO: `int` would probably be better here:
34 | reveal_type(y) # revealed: int | Unknown
| ^ `int | Unknown`
|
```

View File

@@ -12,54 +12,56 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Iterator:
2 | def __next__(self) -> bytes:
3 | return b"foo"
4 |
5 | def _(flag: bool, flag2: bool):
6 | class Iterable1:
7 | if flag:
8 | def __getitem__(self, item: int) -> str:
9 | return "foo"
10 | else:
11 | __getitem__: None = None
12 |
13 | if flag2:
14 | def __iter__(self) -> Iterator:
15 | return Iterator()
16 |
17 | class Iterable2:
18 | if flag:
19 | def __getitem__(self, item: int) -> str:
20 | return "foo"
21 | else:
22 | def __getitem__(self, item: str) -> int:
23 | return 42
24 | if flag2:
25 | def __iter__(self) -> Iterator:
26 | return Iterator()
27 |
28 | # error: [not-iterable]
29 | for x in Iterable1():
30 | # TODO: `bytes | str` might be better
31 | reveal_type(x) # revealed: bytes | str | Unknown
32 |
33 | # error: [not-iterable]
34 | for y in Iterable2():
35 | reveal_type(y) # revealed: bytes | str | int
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> bytes:
5 | return b"foo"
6 |
7 | def _(flag: bool, flag2: bool):
8 | class Iterable1:
9 | if flag:
10 | def __getitem__(self, item: int) -> str:
11 | return "foo"
12 | else:
13 | __getitem__: None = None
14 |
15 | if flag2:
16 | def __iter__(self) -> Iterator:
17 | return Iterator()
18 |
19 | class Iterable2:
20 | if flag:
21 | def __getitem__(self, item: int) -> str:
22 | return "foo"
23 | else:
24 | def __getitem__(self, item: str) -> int:
25 | return 42
26 | if flag2:
27 | def __iter__(self) -> Iterator:
28 | return Iterator()
29 |
30 | # error: [not-iterable]
31 | for x in Iterable1():
32 | # TODO: `bytes | str` might be better
33 | reveal_type(x) # revealed: bytes | str | Unknown
34 |
35 | # error: [not-iterable]
36 | for y in Iterable2():
37 | reveal_type(y) # revealed: bytes | str | int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:29:14
--> src/mdtest_snippet.py:31:14
|
28 | # error: [not-iterable]
29 | for x in Iterable1():
30 | # error: [not-iterable]
31 | for x in Iterable1():
| ^^^^^^^^^^^
30 | # TODO: `bytes | str` might be better
31 | reveal_type(x) # revealed: bytes | str | Unknown
32 | # TODO: `bytes | str` might be better
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
info: It may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
info: rule `not-iterable` is enabled by default
@@ -67,16 +69,42 @@ info: rule `not-iterable` is enabled by default
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:34:14
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:33:21
|
33 | # error: [not-iterable]
34 | for y in Iterable2():
31 | for x in Iterable1():
32 | # TODO: `bytes | str` might be better
33 | reveal_type(x) # revealed: bytes | str | Unknown
| ^ `bytes | str | Unknown`
34 |
35 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:36:14
|
35 | # error: [not-iterable]
36 | for y in Iterable2():
| ^^^^^^^^^^^
35 | reveal_type(y) # revealed: bytes | str | int
37 | reveal_type(y) # revealed: bytes | str | int
|
info: It may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:37:21
|
35 | # error: [not-iterable]
36 | for y in Iterable2():
37 | reveal_type(y) # revealed: bytes | str | int
| ^ `bytes | str | int`
|
```

View File

@@ -12,38 +12,52 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | def _(flag: bool):
2 | class Iterator:
3 | def __next__(self) -> int:
4 | return 42
5 |
6 | class Iterable:
7 | if flag:
8 | def __iter__(self) -> Iterator:
9 | return Iterator()
10 | # invalid signature because it only accepts a `str`,
11 | # but the old-style iteration protocol will pass it an `int`
12 | def __getitem__(self, key: str) -> bytes:
13 | return bytes()
14 |
15 | # error: [not-iterable]
16 | for x in Iterable():
17 | reveal_type(x) # revealed: int | bytes
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class Iterator:
5 | def __next__(self) -> int:
6 | return 42
7 |
8 | class Iterable:
9 | if flag:
10 | def __iter__(self) -> Iterator:
11 | return Iterator()
12 | # invalid signature because it only accepts a `str`,
13 | # but the old-style iteration protocol will pass it an `int`
14 | def __getitem__(self, key: str) -> bytes:
15 | return bytes()
16 |
17 | # error: [not-iterable]
18 | for x in Iterable():
19 | reveal_type(x) # revealed: int | bytes
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` may not be iterable
--> src/mdtest_snippet.py:16:14
--> src/mdtest_snippet.py:18:14
|
15 | # error: [not-iterable]
16 | for x in Iterable():
17 | # error: [not-iterable]
18 | for x in Iterable():
| ^^^^^^^^^^
17 | reveal_type(x) # revealed: int | bytes
19 | reveal_type(x) # revealed: int | bytes
|
info: It may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:21
|
17 | # error: [not-iterable]
18 | for x in Iterable():
19 | reveal_type(x) # revealed: int | bytes
| ^ `int | bytes`
|
```

View File

@@ -12,36 +12,50 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Iterator:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | def _(flag1: bool, flag2: bool):
6 | class Iterable:
7 | if flag1:
8 | def __iter__(self) -> Iterator:
9 | return Iterator()
10 | if flag2:
11 | def __getitem__(self, key: int) -> bytes:
12 | return bytes()
13 |
14 | # error: [not-iterable]
15 | for x in Iterable():
16 | reveal_type(x) # revealed: int | bytes
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | def _(flag1: bool, flag2: bool):
8 | class Iterable:
9 | if flag1:
10 | def __iter__(self) -> Iterator:
11 | return Iterator()
12 | if flag2:
13 | def __getitem__(self, key: int) -> bytes:
14 | return bytes()
15 |
16 | # error: [not-iterable]
17 | for x in Iterable():
18 | reveal_type(x) # revealed: int | bytes
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` may not be iterable
--> src/mdtest_snippet.py:15:14
--> src/mdtest_snippet.py:17:14
|
14 | # error: [not-iterable]
15 | for x in Iterable():
16 | # error: [not-iterable]
17 | for x in Iterable():
| ^^^^^^^^^^
16 | reveal_type(x) # revealed: int | bytes
18 | reveal_type(x) # revealed: int | bytes
|
info: It may not have an `__iter__` method or a `__getitem__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:18:21
|
16 | # error: [not-iterable]
17 | for x in Iterable():
18 | reveal_type(x) # revealed: int | bytes
| ^ `int | bytes`
|
```

View File

@@ -12,38 +12,52 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class TestIter:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | class Test:
6 | def __iter__(self) -> TestIter:
7 | return TestIter()
8 |
9 | class Test2:
10 | def __iter__(self) -> int:
11 | return 42
12 |
13 | def _(flag: bool):
14 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
15 | # error: [not-iterable]
16 | for x in Test() if flag else Test2():
17 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | class TestIter:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | class Test:
8 | def __iter__(self) -> TestIter:
9 | return TestIter()
10 |
11 | class Test2:
12 | def __iter__(self) -> int:
13 | return 42
14 |
15 | def _(flag: bool):
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
17 | # error: [not-iterable]
18 | for x in Test() if flag else Test2():
19 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Test | Test2` may not be iterable
--> src/mdtest_snippet.py:16:14
--> src/mdtest_snippet.py:18:14
|
14 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
15 | # error: [not-iterable]
16 | for x in Test() if flag else Test2():
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
17 | # error: [not-iterable]
18 | for x in Test() if flag else Test2():
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
17 | reveal_type(x) # revealed: int
19 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:21
|
17 | # error: [not-iterable]
18 | for x in Test() if flag else Test2():
19 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,33 +12,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class TestIter:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | class Test:
6 | def __iter__(self) -> TestIter:
7 | return TestIter()
8 |
9 | def _(flag: bool):
10 | # error: [not-iterable]
11 | for x in Test() if flag else 42:
12 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | class TestIter:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | class Test:
8 | def __iter__(self) -> TestIter:
9 | return TestIter()
10 |
11 | def _(flag: bool):
12 | # error: [not-iterable]
13 | for x in Test() if flag else 42:
14 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Test | Literal[42]` may not be iterable
--> src/mdtest_snippet.py:11:14
--> src/mdtest_snippet.py:13:14
|
9 | def _(flag: bool):
10 | # error: [not-iterable]
11 | for x in Test() if flag else 42:
11 | def _(flag: bool):
12 | # error: [not-iterable]
13 | for x in Test() if flag else 42:
| ^^^^^^^^^^^^^^^^^^^^^^
12 | reveal_type(x) # revealed: int
14 | reveal_type(x) # revealed: int
|
info: It may not have an `__iter__` method and it doesn't have a `__getitem__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable]
13 | for x in Test() if flag else 42:
14 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,32 +12,34 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | def _(flag: bool):
2 | class NotIterable:
3 | if flag:
4 | __iter__: int = 1
5 | else:
6 | __iter__: None = None
7 |
8 | # error: [not-iterable]
9 | for x in NotIterable():
10 | pass
11 |
12 | # revealed: Unknown
13 | # error: [possibly-unresolved-reference]
14 | reveal_type(x)
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class NotIterable:
5 | if flag:
6 | __iter__: int = 1
7 | else:
8 | __iter__: None = None
9 |
10 | # error: [not-iterable]
11 | for x in NotIterable():
12 | pass
13 |
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
```
# Diagnostics
```
error[not-iterable]: Object of type `NotIterable` is not iterable
--> src/mdtest_snippet.py:9:14
--> src/mdtest_snippet.py:11:14
|
8 | # error: [not-iterable]
9 | for x in NotIterable():
10 | # error: [not-iterable]
11 | for x in NotIterable():
| ^^^^^^^^^^^^^
10 | pass
12 | pass
|
info: Its `__iter__` attribute has type `int | None`, which is not callable
info: rule `not-iterable` is enabled by default
@@ -46,13 +48,25 @@ info: rule `not-iterable` is enabled by default
```
info[possibly-unresolved-reference]: Name `x` used when possibly not defined
--> src/mdtest_snippet.py:14:17
--> src/mdtest_snippet.py:16:17
|
12 | # revealed: Unknown
13 | # error: [possibly-unresolved-reference]
14 | reveal_type(x)
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
| ^
|
info: rule `possibly-unresolved-reference` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:16:17
|
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
| ^ `Unknown`
|
```

View File

@@ -12,27 +12,41 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Bad:
2 | def __iter__(self) -> int:
3 | return 42
4 |
5 | # error: [not-iterable]
6 | for x in Bad():
7 | reveal_type(x) # revealed: Unknown
1 | from typing_extensions import reveal_type
2 |
3 | class Bad:
4 | def __iter__(self) -> int:
5 | return 42
6 |
7 | # error: [not-iterable]
8 | for x in Bad():
9 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Bad` is not iterable
--> src/mdtest_snippet.py:6:10
--> src/mdtest_snippet.py:8:10
|
5 | # error: [not-iterable]
6 | for x in Bad():
7 | # error: [not-iterable]
8 | for x in Bad():
| ^^^^^
7 | reveal_type(x) # revealed: Unknown
9 | reveal_type(x) # revealed: Unknown
|
info: Its `__iter__` method returns an object of type `int`, which has no `__next__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:17
|
7 | # error: [not-iterable]
8 | for x in Bad():
9 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,32 +12,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Iterator:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | class Iterable:
6 | def __iter__(self, extra_arg) -> Iterator:
7 | return Iterator()
8 |
9 | # error: [not-iterable]
10 | for x in Iterable():
11 | reveal_type(x) # revealed: int
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | class Iterable:
8 | def __iter__(self, extra_arg) -> Iterator:
9 | return Iterator()
10 |
11 | # error: [not-iterable]
12 | for x in Iterable():
13 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` is not iterable
--> src/mdtest_snippet.py:10:10
--> src/mdtest_snippet.py:12:10
|
9 | # error: [not-iterable]
10 | for x in Iterable():
11 | # error: [not-iterable]
12 | for x in Iterable():
| ^^^^^^^^^^
11 | reveal_type(x) # revealed: int
13 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method has an invalid signature
info: Expected signature `def __iter__(self): ...`
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:13:17
|
11 | # error: [not-iterable]
12 | for x in Iterable():
13 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,40 +12,42 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | class Iterator1:
2 | def __next__(self, extra_arg) -> int:
3 | return 42
4 |
5 | class Iterator2:
6 | __next__: None = None
7 |
8 | class Iterable1:
9 | def __iter__(self) -> Iterator1:
10 | return Iterator1()
11 |
12 | class Iterable2:
13 | def __iter__(self) -> Iterator2:
14 | return Iterator2()
15 |
16 | # error: [not-iterable]
17 | for x in Iterable1():
18 | reveal_type(x) # revealed: int
19 |
20 | # error: [not-iterable]
21 | for y in Iterable2():
22 | reveal_type(y) # revealed: Unknown
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator1:
4 | def __next__(self, extra_arg) -> int:
5 | return 42
6 |
7 | class Iterator2:
8 | __next__: None = None
9 |
10 | class Iterable1:
11 | def __iter__(self) -> Iterator1:
12 | return Iterator1()
13 |
14 | class Iterable2:
15 | def __iter__(self) -> Iterator2:
16 | return Iterator2()
17 |
18 | # error: [not-iterable]
19 | for x in Iterable1():
20 | reveal_type(x) # revealed: int
21 |
22 | # error: [not-iterable]
23 | for y in Iterable2():
24 | reveal_type(y) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` is not iterable
--> src/mdtest_snippet.py:17:10
--> src/mdtest_snippet.py:19:10
|
16 | # error: [not-iterable]
17 | for x in Iterable1():
18 | # error: [not-iterable]
19 | for x in Iterable1():
| ^^^^^^^^^^^
18 | reveal_type(x) # revealed: int
20 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method
info: Expected signature for `__next__` is `def __next__(self): ...`
@@ -54,15 +56,41 @@ info: rule `not-iterable` is enabled by default
```
```
error[not-iterable]: Object of type `Iterable2` is not iterable
--> src/mdtest_snippet.py:21:10
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:20:17
|
20 | # error: [not-iterable]
21 | for y in Iterable2():
18 | # error: [not-iterable]
19 | for x in Iterable1():
20 | reveal_type(x) # revealed: int
| ^ `int`
21 |
22 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` is not iterable
--> src/mdtest_snippet.py:23:10
|
22 | # error: [not-iterable]
23 | for y in Iterable2():
| ^^^^^^^^^^^
22 | reveal_type(y) # revealed: Unknown
24 | reveal_type(y) # revealed: Unknown
|
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:24:17
|
22 | # error: [not-iterable]
23 | for y in Iterable2():
24 | reveal_type(y) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -13,38 +13,78 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function
```
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T", bound=int)
4 |
5 | def f(x: T) -> T:
6 | return x
7 |
8 | reveal_type(f(1)) # revealed: Literal[1]
9 | reveal_type(f(True)) # revealed: Literal[True]
10 | # error: [invalid-argument-type]
11 | reveal_type(f("string")) # revealed: Unknown
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", bound=int)
5 |
6 | def f(x: T) -> T:
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: Literal[1]
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
```
# Diagnostics
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:11:15
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:13
|
9 | reveal_type(f(True)) # revealed: Literal[True]
10 | # error: [invalid-argument-type]
11 | reveal_type(f("string")) # revealed: Unknown
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: Literal[1]
| ^^^^ `Literal[1]`
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
9 | reveal_type(f(1)) # revealed: Literal[1]
10 | reveal_type(f(True)) # revealed: Literal[True]
| ^^^^^^^ `Literal[True]`
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:12:13
|
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:12:15
|
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound `int` of type variable `T`
|
info: Type variable defined here
--> src/mdtest_snippet.py:3:1
--> src/mdtest_snippet.py:4:1
|
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T", bound=int)
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", bound=int)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 |
5 | def f(x: T) -> T:
5 |
6 | def f(x: T) -> T:
|
info: rule `invalid-argument-type` is enabled by default

View File

@@ -13,39 +13,93 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function
```
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T", int, None)
4 |
5 | def f(x: T) -> T:
6 | return x
7 |
8 | reveal_type(f(1)) # revealed: int
9 | reveal_type(f(True)) # revealed: int
10 | reveal_type(f(None)) # revealed: None
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", int, None)
5 |
6 | def f(x: T) -> T:
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: int
10 | reveal_type(f(True)) # revealed: int
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
```
# Diagnostics
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:12:15
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:13
|
10 | reveal_type(f(None)) # revealed: None
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: int
| ^^^^ `int`
10 | reveal_type(f(True)) # revealed: int
11 | reveal_type(f(None)) # revealed: None
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
9 | reveal_type(f(1)) # revealed: int
10 | reveal_type(f(True)) # revealed: int
| ^^^^^^^ `int`
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:11:13
|
9 | reveal_type(f(1)) # revealed: int
10 | reveal_type(f(True)) # revealed: int
11 | reveal_type(f(None)) # revealed: None
| ^^^^^^^ `None`
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:13:13
|
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:13:15
|
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy constraints (`int`, `None`) of type variable `T`
|
info: Type variable defined here
--> src/mdtest_snippet.py:3:1
--> src/mdtest_snippet.py:4:1
|
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T", int, None)
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", int, None)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 |
5 | def f(x: T) -> T:
5 |
6 | def f(x: T) -> T:
|
info: rule `invalid-argument-type` is enabled by default

View File

@@ -25,6 +25,45 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:6:13
|
4 | return x
5 |
6 | reveal_type(f(1)) # revealed: Literal[1]
| ^^^^ `Literal[1]`
7 | reveal_type(f(True)) # revealed: Literal[True]
8 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:7:13
|
6 | reveal_type(f(1)) # revealed: Literal[1]
7 | reveal_type(f(True)) # revealed: Literal[True]
| ^^^^^^^ `Literal[True]`
8 | # error: [invalid-argument-type]
9 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:13
|
7 | reveal_type(f(True)) # revealed: Literal[True]
8 | # error: [invalid-argument-type]
9 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:9:15

View File

@@ -26,6 +26,59 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:6:13
|
4 | return x
5 |
6 | reveal_type(f(1)) # revealed: int
| ^^^^ `int`
7 | reveal_type(f(True)) # revealed: int
8 | reveal_type(f(None)) # revealed: None
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:7:13
|
6 | reveal_type(f(1)) # revealed: int
7 | reveal_type(f(True)) # revealed: int
| ^^^^^^^ `int`
8 | reveal_type(f(None)) # revealed: None
9 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:13
|
6 | reveal_type(f(1)) # revealed: int
7 | reveal_type(f(True)) # revealed: int
8 | reveal_type(f(None)) # revealed: None
| ^^^^^^^ `None`
9 | # error: [invalid-argument-type]
10 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
8 | reveal_type(f(None)) # revealed: None
9 | # error: [invalid-argument-type]
10 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:10:15

View File

@@ -1,70 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Boolean parameters must be unambiguous
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing_extensions import TypeVar
2 |
3 | def cond() -> bool:
4 | return True
5 |
6 | # error: [invalid-legacy-type-variable]
7 | T = TypeVar("T", covariant=cond())
8 |
9 | # error: [invalid-legacy-type-variable]
10 | U = TypeVar("U", contravariant=cond())
11 |
12 | # error: [invalid-legacy-type-variable]
13 | V = TypeVar("V", infer_variance=cond())
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `covariant` parameter of `TypeVar` cannot have an ambiguous truthiness
--> src/mdtest_snippet.py:7:28
|
6 | # error: [invalid-legacy-type-variable]
7 | T = TypeVar("T", covariant=cond())
| ^^^^^^
8 |
9 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: The `contravariant` parameter of `TypeVar` cannot have an ambiguous truthiness
--> src/mdtest_snippet.py:10:32
|
9 | # error: [invalid-legacy-type-variable]
10 | U = TypeVar("U", contravariant=cond())
| ^^^^^^
11 |
12 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: The `infer_variance` parameter of `TypeVar` cannot have an ambiguous truthiness
--> src/mdtest_snippet.py:13:33
|
12 | # error: [invalid-legacy-type-variable]
13 | V = TypeVar("V", infer_variance=cond())
| ^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot be both covariant and contravariant
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", covariant=True, contravariant=True)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` cannot be both covariant and contravariant
--> src/mdtest_snippet.py:4:5
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", covariant=True, contravariant=True)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have both bound and constraint
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int, str, bound=bytes)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` cannot have both a bound and constraints
--> src/mdtest_snippet.py:4:5
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int, str, bound=bytes)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have only one constraint
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` cannot have exactly one constraint
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int)
| ^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid feature for this Python version
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", default=int)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `default` parameter of `typing.TypeVar` was added in Python 3.13
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", default=int)
| ^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid keyword arguments
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", invalid_keyword=True)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: Unknown keyword argument `invalid_keyword` in `TypeVar` creation
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", invalid_keyword=True)
| ^^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,52 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must be directly assigned to a variable
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T")
4 | # error: [invalid-legacy-type-variable]
5 | U: TypeVar = TypeVar("U")
6 |
7 | # error: [invalid-legacy-type-variable]
8 | tuple_with_typevar = ("foo", TypeVar("W"))
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
--> src/mdtest_snippet.py:5:14
|
3 | T = TypeVar("T")
4 | # error: [invalid-legacy-type-variable]
5 | U: TypeVar = TypeVar("U")
| ^^^^^^^^^^^^
6 |
7 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
--> src/mdtest_snippet.py:8:30
|
7 | # error: [invalid-legacy-type-variable]
8 | tuple_with_typevar = ("foo", TypeVar("W"))
| ^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must have a name
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar()
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` is required.
--> src/mdtest_snippet.py:4:5
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar()
| ^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Name can't be given more than once
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", name="T")
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` can only be provided once.
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", name="T")
| ^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,52 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - No variadic arguments
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | types = (int, str)
4 |
5 | # error: [invalid-legacy-type-variable]
6 | T = TypeVar("T", *types)
7 |
8 | # error: [invalid-legacy-type-variable]
9 | S = TypeVar("S", **{"bound": int})
```
# Diagnostics
```
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
--> src/mdtest_snippet.py:6:18
|
5 | # error: [invalid-legacy-type-variable]
6 | T = TypeVar("T", *types)
| ^^^^^^
7 |
8 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
--> src/mdtest_snippet.py:9:18
|
8 | # error: [invalid-legacy-type-variable]
9 | S = TypeVar("S", **{"bound": int})
| ^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -1,33 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - `TypeVar` parameter must match variable name
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("Q")
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The name of a `TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)
--> src/mdtest_snippet.py:4:1
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("Q")
| ^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -12,39 +12,67 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
## mdtest_snippet.py
```
1 | def returns_bool() -> bool:
2 | return True
3 |
4 | class A: ...
5 | class B: ...
6 |
7 | if returns_bool():
8 | x = A
9 | else:
10 | x = B
11 |
12 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
1 | from typing_extensions import reveal_type
2 |
3 | def returns_bool() -> bool:
4 | return True
5 |
6 | class A: ...
7 | class B: ...
8 |
9 | if returns_bool():
10 | x = A
11 | else:
12 | x = B
13 |
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
15 | class Foo(x): ...
16 |
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
15 |
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
17 | class Foo(x): ...
18 |
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
```
# Diagnostics
```
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
--> src/mdtest_snippet.py:15:11
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:13
|
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
15 | class Foo(x): ...
12 | x = B
13 |
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
| ^ `<class 'A'> | <class 'B'>`
15 |
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
```
```
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
--> src/mdtest_snippet.py:17:11
|
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
17 | class Foo(x): ...
| ^
16 |
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
18 |
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: ty cannot resolve a consistent MRO for class `Foo` due to this base
info: Only class objects or `Any` are supported as class bases
info: rule `unsupported-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:13
|
17 | class Foo(x): ...
18 |
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
|
```

View File

@@ -12,147 +12,167 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
## mdtest_snippet.py
```
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
1 | from typing_extensions import reveal_type
2 |
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
4 |
5 | class Spam: ...
6 | class Eggs: ...
7 | class Bar: ...
8 | class Baz: ...
9 |
10 | # fmt: off
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
6 |
7 | class Spam: ...
8 | class Eggs: ...
9 | class Bar: ...
10 | class Baz: ...
11 |
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
15 | Spam,
16 | Eggs,
17 | Bar,
18 | Baz,
19 | Spam,
20 | Eggs,
21 | ): ...
22 |
23 | # fmt: on
12 | # fmt: off
13 |
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
17 | Spam,
18 | Eggs,
19 | Bar,
20 | Baz,
21 | Spam,
22 | Eggs,
23 | ): ...
24 |
25 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
25 | # fmt: on
26 |
27 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
29 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
28 |
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
31 |
32 | # fmt: off
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
33 |
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette(
36 | Eggs,
37 | Ham,
38 | Spam,
39 | Eggs,
40 | Mushrooms,
41 | Bar,
42 | Eggs,
43 | Baz,
34 | # fmt: off
35 |
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
38 | Eggs,
39 | Ham,
40 | Spam,
41 | Eggs,
42 | Mushrooms,
43 | Bar,
44 | Eggs,
45 | ): ...
46 |
47 | # fmt: off
48 | # fmt: off
49 |
50 | class A: ...
45 | Baz,
46 | Eggs,
47 | ): ...
48 |
49 | # fmt: off
50 | # fmt: off
51 |
52 | class B( # type: ignore[duplicate-base]
53 | A,
54 | A,
55 | ): ...
56 |
57 | class C(
58 | A,
59 | A
60 | ): # type: ignore[duplicate-base]
61 | x: int
62 |
63 | # fmt: on
64 | # fmt: off
65 |
66 | # error: [duplicate-base]
67 | class D(
68 | A,
69 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base]
71 | ): ...
72 |
73 | # error: [duplicate-base]
74 | class E(
75 | A,
76 | A
77 | ):
78 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base]
80 |
81 | # fmt: on
52 | class A: ...
53 |
54 | class B( # type: ignore[duplicate-base]
55 | A,
56 | A,
57 | ): ...
58 |
59 | class C(
60 | A,
61 | A
62 | ): # type: ignore[duplicate-base]
63 | x: int
64 |
65 | # fmt: on
66 | # fmt: off
67 |
68 | # error: [duplicate-base]
69 | class D(
70 | A,
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
73 | ): ...
74 |
75 | # error: [duplicate-base]
76 | class E(
77 | A,
78 | A
79 | ):
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
82 |
83 | # fmt: on
```
# Diagnostics
```
error[duplicate-base]: Duplicate base class `str`
--> src/mdtest_snippet.py:1:7
--> src/mdtest_snippet.py:3:7
|
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
| ^^^^^^^^^^^^^
1 | from typing_extensions import reveal_type
2 |
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
| ^^^^^^^^^^^^^
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: The definition of class `Foo` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:1:11
--> src/mdtest_snippet.py:3:11
|
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
1 | from typing_extensions import reveal_type
2 |
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
| --- ^^^ Class `str` later repeated here
| |
| Class `str` first included in bases list here
2 |
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: rule `duplicate-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:5:13
|
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
6 |
7 | class Spam: ...
|
```
```
error[duplicate-base]: Duplicate base class `Spam`
--> src/mdtest_snippet.py:14:7
--> src/mdtest_snippet.py:16:7
|
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
| _______^
15 | | Spam,
16 | | Eggs,
17 | | Bar,
18 | | Baz,
19 | | Spam,
20 | | Eggs,
21 | | ): ...
17 | | Spam,
18 | | Eggs,
19 | | Bar,
20 | | Baz,
21 | | Spam,
22 | | Eggs,
23 | | ): ...
| |_^
22 |
23 | # fmt: on
24 |
25 | # fmt: on
|
info: The definition of class `Ham` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:15:5
--> src/mdtest_snippet.py:17:5
|
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
15 | Spam,
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
17 | Spam,
| ---- Class `Spam` first included in bases list here
16 | Eggs,
17 | Bar,
18 | Baz,
19 | Spam,
18 | Eggs,
19 | Bar,
20 | Baz,
21 | Spam,
| ^^^^ Class `Spam` later repeated here
20 | Eggs,
21 | ): ...
22 | Eggs,
23 | ): ...
|
info: rule `duplicate-base` is enabled by default
@@ -160,106 +180,134 @@ info: rule `duplicate-base` is enabled by default
```
error[duplicate-base]: Duplicate base class `Eggs`
--> src/mdtest_snippet.py:14:7
--> src/mdtest_snippet.py:16:7
|
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
| _______^
15 | | Spam,
16 | | Eggs,
17 | | Bar,
18 | | Baz,
19 | | Spam,
20 | | Eggs,
21 | | ): ...
17 | | Spam,
18 | | Eggs,
19 | | Bar,
20 | | Baz,
21 | | Spam,
22 | | Eggs,
23 | | ): ...
| |_^
22 |
23 | # fmt: on
24 |
25 | # fmt: on
|
info: The definition of class `Ham` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:16:5
--> src/mdtest_snippet.py:18:5
|
14 | class Ham(
15 | Spam,
16 | Eggs,
16 | class Ham(
17 | Spam,
18 | Eggs,
| ---- Class `Eggs` first included in bases list here
17 | Bar,
18 | Baz,
19 | Spam,
20 | Eggs,
19 | Bar,
20 | Baz,
21 | Spam,
22 | Eggs,
| ^^^^ Class `Eggs` later repeated here
21 | ): ...
23 | ): ...
|
info: rule `duplicate-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:27:13
|
25 | # fmt: on
26 |
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^ `tuple[<class 'Ham'>, Unknown, <class 'object'>]`
28 |
29 | class Mushrooms: ...
|
```
```
error[duplicate-base]: Duplicate base class `Mushrooms`
--> src/mdtest_snippet.py:28:7
--> src/mdtest_snippet.py:30:7
|
27 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
info: The definition of class `Omelette` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:28:28
--> src/mdtest_snippet.py:30:28
|
27 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| --------- ^^^^^^^^^ Class `Mushrooms` later repeated here
| |
| Class `Mushrooms` first included in bases list here
29 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
info: rule `duplicate-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:32:13
|
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^^^^^^ `tuple[<class 'Omelette'>, Unknown, <class 'object'>]`
33 |
34 | # fmt: off
|
```
```
error[duplicate-base]: Duplicate base class `Eggs`
--> src/mdtest_snippet.py:35:7
--> src/mdtest_snippet.py:37:7
|
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette(
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
| _______^
36 | | Eggs,
37 | | Ham,
38 | | Spam,
39 | | Eggs,
40 | | Mushrooms,
41 | | Bar,
42 | | Eggs,
43 | | Baz,
38 | | Eggs,
39 | | Ham,
40 | | Spam,
41 | | Eggs,
42 | | Mushrooms,
43 | | Bar,
44 | | Eggs,
45 | | ): ...
45 | | Baz,
46 | | Eggs,
47 | | ): ...
| |_^
46 |
47 | # fmt: off
48 |
49 | # fmt: off
|
info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:36:5
--> src/mdtest_snippet.py:38:5
|
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette(
36 | Eggs,
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
38 | Eggs,
| ---- Class `Eggs` first included in bases list here
37 | Ham,
38 | Spam,
39 | Eggs,
39 | Ham,
40 | Spam,
41 | Eggs,
| ^^^^ Class `Eggs` later repeated here
40 | Mushrooms,
41 | Bar,
42 | Eggs,
| ^^^^ Class `Eggs` later repeated here
43 | Baz,
42 | Mushrooms,
43 | Bar,
44 | Eggs,
| ^^^^ Class `Eggs` later repeated here
45 | ): ...
45 | Baz,
46 | Eggs,
| ^^^^ Class `Eggs` later repeated here
47 | ): ...
|
info: rule `duplicate-base` is enabled by default
@@ -267,30 +315,30 @@ info: rule `duplicate-base` is enabled by default
```
error[duplicate-base]: Duplicate base class `A`
--> src/mdtest_snippet.py:67:7
--> src/mdtest_snippet.py:69:7
|
66 | # error: [duplicate-base]
67 | class D(
68 | # error: [duplicate-base]
69 | class D(
| _______^
68 | | A,
69 | | # error: [unused-ignore-comment]
70 | | A, # type: ignore[duplicate-base]
71 | | ): ...
70 | | A,
71 | | # error: [unused-ignore-comment]
72 | | A, # type: ignore[duplicate-base]
73 | | ): ...
| |_^
72 |
73 | # error: [duplicate-base]
74 |
75 | # error: [duplicate-base]
|
info: The definition of class `D` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:68:5
--> src/mdtest_snippet.py:70:5
|
66 | # error: [duplicate-base]
67 | class D(
68 | A,
68 | # error: [duplicate-base]
69 | class D(
70 | A,
| - Class `A` first included in bases list here
69 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base]
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
| ^ Class `A` later repeated here
71 | ): ...
73 | ): ...
|
info: rule `duplicate-base` is enabled by default
@@ -298,42 +346,42 @@ info: rule `duplicate-base` is enabled by default
```
info[unused-ignore-comment]
--> src/mdtest_snippet.py:70:9
--> src/mdtest_snippet.py:72:9
|
68 | A,
69 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base]
70 | A,
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
71 | ): ...
73 | ): ...
|
```
```
error[duplicate-base]: Duplicate base class `A`
--> src/mdtest_snippet.py:74:7
--> src/mdtest_snippet.py:76:7
|
73 | # error: [duplicate-base]
74 | class E(
75 | # error: [duplicate-base]
76 | class E(
| _______^
75 | | A,
76 | | A
77 | | ):
77 | | A,
78 | | A
79 | | ):
| |_^
78 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base]
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
|
info: The definition of class `E` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:75:5
--> src/mdtest_snippet.py:77:5
|
73 | # error: [duplicate-base]
74 | class E(
75 | A,
75 | # error: [duplicate-base]
76 | class E(
77 | A,
| - Class `A` first included in bases list here
76 | A
78 | A
| ^ Class `A` later repeated here
77 | ):
78 | # error: [unused-ignore-comment]
79 | ):
80 | # error: [unused-ignore-comment]
|
info: rule `duplicate-base` is enabled by default
@@ -341,14 +389,14 @@ info: rule `duplicate-base` is enabled by default
```
info[unused-ignore-comment]
--> src/mdtest_snippet.py:79:13
--> src/mdtest_snippet.py:81:13
|
77 | ):
78 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base]
79 | ):
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
80 |
81 | # fmt: on
82 |
83 | # fmt: on
|
```

View File

@@ -136,3 +136,48 @@ info: (x: B, /, **kwargs: int) -> B
info: rule `no-matching-overload` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:9
|
6 | # error: [no-matching-overload]
7 | # revealed: Unknown
8 | / f(
9 | | A(),
10 | | a1=a,
11 | | a2=a,
12 | | a3=a,
13 | | a4=a,
14 | | a5=a,
15 | | a6=a,
16 | | a7=a,
17 | | a8=a,
18 | | a9=a,
19 | | a10=a,
20 | | a11=a,
21 | | a12=a,
22 | | a13=a,
23 | | a14=a,
24 | | a15=a,
25 | | a16=a,
26 | | a17=a,
27 | | a18=a,
28 | | a19=a,
29 | | a20=a,
30 | | a21=a,
31 | | a22=a,
32 | | a23=a,
33 | | a24=a,
34 | | a25=a,
35 | | a26=a,
36 | | a27=a,
37 | | a28=a,
38 | | a29=a,
39 | | a30=a,
40 | | )
| |_________^ `Unknown`
41 | )
|
```

View File

@@ -12,7 +12,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
## mdtest_snippet.py
```
1 | from typing_extensions import Protocol
1 | from typing_extensions import Protocol, reveal_type
2 |
3 | # error: [call-non-callable]
4 | reveal_type(Protocol()) # revealed: Unknown
@@ -36,7 +36,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
22 |
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
24 | def f(x: type[MyProtocol]):
25 | reveal_type(x()) # revealed: @Todo(type[T] for protocols)
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
26 | # (which doesn't work well with snapshots)
27 | x()
```
# Diagnostics
@@ -55,6 +57,19 @@ info: rule `call-non-callable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:4:13
|
3 | # error: [call-non-callable]
4 | reveal_type(Protocol()) # revealed: Unknown
| ^^^^^^^^^^ `Unknown`
5 |
6 | class MyProtocol(Protocol):
|
```
```
error[call-non-callable]: Cannot instantiate class `MyProtocol`
--> src/mdtest_snippet.py:10:13
@@ -78,6 +93,19 @@ info: rule `call-non-callable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
| ^^^^^^^^^^^^ `MyProtocol`
11 |
12 | class GenericProtocol[T](Protocol):
|
```
```
error[call-non-callable]: Cannot instantiate class `GenericProtocol`
--> src/mdtest_snippet.py:16:13
@@ -99,3 +127,43 @@ info: Protocol classes cannot be instantiated
info: rule `call-non-callable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:16:13
|
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
| ^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol[int]`
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:13
|
17 | class SubclassOfMyProtocol(MyProtocol): ...
18 |
19 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
| ^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfMyProtocol`
20 |
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:23:13
|
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
22 |
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfGenericProtocol[int]`
24 | def f(x: type[MyProtocol]):
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
```

View File

@@ -12,7 +12,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
## mdtest_snippet.py
```
1 | from typing_extensions import Protocol
1 | from typing_extensions import Protocol, reveal_type
2 |
3 | class HasX(Protocol):
4 | x: int
@@ -69,7 +69,7 @@ error[invalid-argument-type]: Class `HasX` cannot be used as the second argument
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
--> src/mdtest_snippet.py:3:7
|
1 | from typing_extensions import Protocol
1 | from typing_extensions import Protocol, reveal_type
2 |
3 | class HasX(Protocol):
| ^^^^^^^^^^^^^^ `HasX` declared here
@@ -81,6 +81,34 @@ info: rule `invalid-argument-type` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:21
|
6 | def f(arg: object, arg2: type):
7 | if isinstance(arg, HasX): # error: [invalid-argument-type]
8 | reveal_type(arg) # revealed: HasX
| ^^^ `HasX`
9 | else:
10 | reveal_type(arg) # revealed: ~HasX
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:21
|
8 | reveal_type(arg) # revealed: HasX
9 | else:
10 | reveal_type(arg) # revealed: ~HasX
| ^^^ `~HasX`
11 |
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
```
```
error[invalid-argument-type]: Class `HasX` cannot be used as the second argument to `issubclass`
--> src/mdtest_snippet.py:12:8
@@ -95,7 +123,7 @@ error[invalid-argument-type]: Class `HasX` cannot be used as the second argument
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
--> src/mdtest_snippet.py:3:7
|
1 | from typing_extensions import Protocol
1 | from typing_extensions import Protocol, reveal_type
2 |
3 | class HasX(Protocol):
| ^^^^^^^^^^^^^^ `HasX` declared here
@@ -106,3 +134,110 @@ info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable
info: rule `invalid-argument-type` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:13:21
|
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
13 | reveal_type(arg2) # revealed: type[HasX]
| ^^^^ `type[HasX]`
14 | else:
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:15:21
|
13 | reveal_type(arg2) # revealed: type[HasX]
14 | else:
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
| ^^^^ `type & ~type[HasX]`
16 | from typing import runtime_checkable
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:24:21
|
22 | def f(arg: object):
23 | if isinstance(arg, RuntimeCheckableHasX): # no error!
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
| ^^^ `RuntimeCheckableHasX`
25 | else:
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:26:21
|
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
25 | else:
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
| ^^^ `~RuntimeCheckableHasX`
27 | @runtime_checkable
28 | class OnlyMethodMembers(Protocol):
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:33:21
|
31 | def f(arg1: type, arg2: type):
32 | if issubclass(arg1, RuntimeCheckableHasX): # TODO: should emit an error here (has non-method members)
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
| ^^^^ `type[RuntimeCheckableHasX]`
34 | else:
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:35:21
|
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
34 | else:
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
| ^^^^ `type & ~type[RuntimeCheckableHasX]`
36 |
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:38:21
|
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
| ^^^^ `type[OnlyMethodMembers]`
39 | else:
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:40:21
|
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
39 | else:
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
| ^^^^ `type & ~type[OnlyMethodMembers]`
|
```

View File

@@ -13,7 +13,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
```
1 | import sys
2 | from typing_extensions import Protocol, get_protocol_members
2 | from typing_extensions import Protocol, get_protocol_members, reveal_type
3 |
4 | class Foo(Protocol):
5 | if sys.version_info >= (3, 10):
@@ -43,7 +43,7 @@ warning[ambiguous-protocol-member]: Cannot assign to undeclared variable in the
info: Assigning to an undeclared variable in a protocol class leads to an ambiguous interface
--> src/mdtest_snippet.py:4:7
|
2 | from typing_extensions import Protocol, get_protocol_members
2 | from typing_extensions import Protocol, get_protocol_members, reveal_type
3 |
4 | class Foo(Protocol):
| ^^^^^^^^^^^^^ `Foo` declared as a protocol here
@@ -54,3 +54,15 @@ info: No declarations found for `e` in the body of `Foo` or any of its superclas
info: rule `ambiguous-protocol-member` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:13
|
12 | def f(self) -> None: ...
13 |
14 | reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `frozenset[Literal["d", "e", "f"]]`
|
```

View File

@@ -1,217 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: super.md - Super - Basic Usage - Explicit Super Object
mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
---
# Python source files
## mdtest_snippet.py
```
1 | class A:
2 | def a(self): ...
3 | aa: int = 1
4 |
5 | class B(A):
6 | def b(self): ...
7 | bb: int = 2
8 |
9 | class C(B):
10 | def c(self): ...
11 | cc: int = 3
12 |
13 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
14 |
15 | super(C, C()).a
16 | super(C, C()).b
17 | super(C, C()).c # error: [unresolved-attribute]
18 |
19 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute]
21 | super(B, C()).c # error: [unresolved-attribute]
22 |
23 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute]
26 |
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
28 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
29 | reveal_type(super(C, C()).aa) # revealed: int
30 | reveal_type(super(C, C()).bb) # revealed: int
31 | import types
32 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
33 |
34 | def f(): ...
35 |
36 | class Foo[T]:
37 | def method(self): ...
38 | @property
39 | def some_property(self): ...
40 |
41 | type Alias = int
42 |
43 | class SomeTypedDict(TypedDict):
44 | x: int
45 | y: bytes
46 |
47 | # revealed: <super: <class 'object'>, FunctionType>
48 | reveal_type(super(object, f))
49 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
50 | reveal_type(super(object, types.FunctionType.__get__))
51 | # revealed: <super: <class 'object'>, GenericAlias>
52 | reveal_type(super(object, Foo[int]))
53 | # revealed: <super: <class 'object'>, _SpecialForm>
54 | reveal_type(super(object, Literal))
55 | # revealed: <super: <class 'object'>, TypeAliasType>
56 | reveal_type(super(object, Alias))
57 | # revealed: <super: <class 'object'>, MethodType>
58 | reveal_type(super(object, Foo().method))
59 | # revealed: <super: <class 'object'>, property>
60 | reveal_type(super(object, Foo.some_property))
61 |
62 | def g(x: object) -> TypeIs[list[object]]:
63 | return isinstance(x, list)
64 |
65 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
66 | if hasattr(x, "bar"):
67 | # revealed: <Protocol with members 'bar'>
68 | reveal_type(x)
69 | # error: [invalid-super-argument]
70 | # revealed: Unknown
71 | reveal_type(super(object, x))
72 |
73 | # error: [invalid-super-argument]
74 | # revealed: Unknown
75 | reveal_type(super(object, z))
76 |
77 | is_list = g(x)
78 | # revealed: TypeIs[list[object] @ x]
79 | reveal_type(is_list)
80 | # revealed: <super: <class 'object'>, bool>
81 | reveal_type(super(object, is_list))
82 |
83 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
84 | reveal_type(super(object, y))
```
# Diagnostics
```
error[unresolved-attribute]: Type `<super: <class 'C'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:17:1
|
15 | super(C, C()).a
16 | super(C, C()).b
17 | super(C, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
18 |
19 | super(B, C()).a
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `b`
--> src/mdtest_snippet.py:20:1
|
19 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
21 | super(B, C()).c # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:21:1
|
19 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute]
21 | super(B, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
22 |
23 | super(A, C()).a # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `a`
--> src/mdtest_snippet.py:23:1
|
21 | super(B, C()).c # error: [unresolved-attribute]
22 |
23 | super(A, C()).a # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
24 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `b`
--> src/mdtest_snippet.py:24:1
|
23 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
25 | super(A, C()).c # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:25:1
|
23 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
26 |
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call
--> src/mdtest_snippet.py:71:21
|
69 | # error: [invalid-super-argument]
70 | # revealed: Unknown
71 | reveal_type(super(object, x))
| ^^^^^^^^^^^^^^^^
72 |
73 | # error: [invalid-super-argument]
|
info: rule `invalid-super-argument` is enabled by default
```
```
error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call
--> src/mdtest_snippet.py:75:17
|
73 | # error: [invalid-super-argument]
74 | # revealed: Unknown
75 | reveal_type(super(object, z))
| ^^^^^^^^^^^^^^^^
76 |
77 | is_list = g(x)
|
info: rule `invalid-super-argument` is enabled by default
```

Some files were not shown because too many files have changed in this diff Show More