Compare commits

..

31 Commits

Author SHA1 Message Date
Carl Meyer
89228c344c [WIP] add recursive visitor to more type methods 2025-07-27 22:38:11 -07:00
renovate[bot]
ef3a195f28 Update dependency ruff to v0.12.5 (#19584) 2025-07-27 22:22:23 -04:00
Dylan
008bbfdf5a Disallow implicit concatenation of t-strings and other string types (#19485)
As of [this cpython PR](https://github.com/python/cpython/pull/135996),
it is not allowed to concatenate t-strings with non-t-strings,
implicitly or explicitly. Expressions such as `"foo" t"{bar}"` are now
syntax errors.

This PR updates some AST nodes and parsing to reflect this change.

The structural change is that `TStringPart` is no longer needed, since,
as in the case of `BytesStringLiteral`, the only possibilities are that
we have a single `TString` or a vector of such (representing an implicit
concatenation of t-strings). This removes a level of nesting from many
AST expressions (which is what all the snapshot changes reflect), and
simplifies some logic in the implementation of visitors, for example.

The other change of note is in the parser. When we meet an implicit
concatenation of string-like literals, we now count the number of
t-string literals. If these do not exhaust the total number of
implicitly concatenated pieces, then we emit a syntax error. To recover
from this syntax error, we encode any t-string pieces as _invalid_
string literals (which means we flag them as invalid, record their
range, and record the value as `""`). Note that if at least one of the
pieces is an f-string we prefer to parse the entire string as an
f-string; otherwise we parse it as a string.

This logic is exactly the same as how we currently treat
`BytesStringLiteral` parsing and error recovery - and carries with it
the same pros and cons.

Finally, note that I have not implemented any changes in the
implementation of the formatter. As far as I can tell, none are needed.
I did change a few of the fixtures so that we are always concatenating
t-strings with t-strings.
2025-07-27 12:41:03 +00:00
Alex Waygood
df5eba7583 [ty] Mark all_type_assignable_to_iterable_are_iterable as flaky (#19574) 2025-07-27 11:04:13 +00:00
Micha Reiser
469c50b0b7 [ty] Support stdlib files in playground (#19557) 2025-07-26 19:33:38 +01:00
UnboundVariable
738246627f [ty] Implemented support for "selection range" language server feature (#19567)
This PR adds support for the "selection range" language server feature.
This feature was recently requested by a ty user in [this feature
request](https://github.com/astral-sh/ty/issues/882).

This feature allows a client to implement "smart selection expansion"
based on the structure of the parse tree. For example, if you type
"shift-ctrl-right-arrow" in VS Code, the current selection will be
expanded to include the parent AST node. Conversely,
"shift-ctrl-left-arrow" shrinks the selection.

We will probably need to tune the granularity of selection expansion
based on user feedback. The initial implementation includes most AST
nodes, but users may find this to be too fine-grained. We have the
option of skipping some AST nodes that are not as meaningful when
editing code.

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-26 09:08:36 -07:00
Douglas Creager
e867830848 [ty] Don't include already-bound legacy typevars in function generic context (#19558)
We now correctly exclude legacy typevars from enclosing scopes when
constructing the generic context for a generic function.

more detail:

A function is generic if it refers to legacy typevars in its signature:

```py
from typing import TypeVar

T = TypeVar("T")

def f(t: T) -> T:
    return t
```

Generic functions are allowed to appear inside of other generic
contexts. When they do, they can refer to the typevars of those
enclosing generic contexts, and that should not rebind the typevar:

```py
from typing import TypeVar, Generic

T = TypeVar("T")
U = TypeVar("U")

class C(Generic[T]):
    @staticmethod
    def method(t: T, u: U) -> None: ...

# revealed: def method(t: int, u: U) -> None
reveal_type(C[int].method)
```

This substitution was already being performed correctly, but we were
also still including the enclosing legacy typevars in the method's own
generic context, which can be seen via `ty_extensions.generic_context`
(which has been updated to work on generic functions and methods):

```py
from ty_extensions import generic_context

# before: tuple[T, U]
# after: tuple[U]
reveal_type(generic_context(C[int].method))
```

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-25 18:14:19 -04:00
Elliot Simpson
72fdb7d439 [flake8-blind-except] Change BLE001 to permit logging.critical(..., exc_info=True). (#19520)
## Summary

Changing `BLE001` (blind-except) so that it does not flag `except`
clauses which include `logging.critical(..., exc_info=True)`.

## Test Plan

It passes the following (whereas the `main` branch does not):
```sh
$ cargo run -p ruff -- check somefile.py --no-cache --select=BLE001
```
```python
# somefile.py

import logging


try:
    print("Hello world!")
except Exception:
    logging.critical("Did not run.", exc_info=True)
```
Related: https://github.com/astral-sh/ruff/issues/19519
2025-07-25 17:52:58 -04:00
Dylan
fbf1dfc782 Reword preview warning for target-version Python 3.14 (#19563)
Small rewording to indicate that core development is done but that we
may add breaking changes.

Feel free to bikeshed!

Test:

```console
❯ echo "t''" | cargo run -p ruff -- check --no-cache --isolated --target-version py314 -
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/ruff check --no-cache --isolated --target-version py314 -`
warning: Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning.
All checks passed!
```
2025-07-25 16:09:45 -05:00
UnboundVariable
a0d8ff51dd [ty] Added support for "document symbols" and "workspace symbols" (#19521)
This PR adds support for "document symbols" and "workspace symbols"
language server features. Most of the logic to implement these features
is shared.

The "document symbols" feature returns a list of all symbols within a
specified source file. Clients can specify whether they want a flat or
hierarchical list. Document symbols are typically presented by a client
in an "outline" form. Here's what this looks like in VS Code, for
example.

<img width="240" height="249" alt="image"
src="https://github.com/user-attachments/assets/82b11f4f-32ec-4165-ba01-d6496ad13bdf"
/>


The "workspace symbols" feature returns a list of all symbols across the
entire workspace that match some user-supplied query string. This allows
the user to quickly find and navigate to any symbol within their code.

<img width="450" height="134" alt="image"
src="https://github.com/user-attachments/assets/aac131e0-9464-4adf-8a6c-829da028c759"
/>

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-25 13:07:38 -07:00
Brent Westbrook
165091a31c Add TextEmitter::with_color and disable colors in unreadable_files test (#19562)
Summary
--

I looked at other uses of `TextEmitter`, and I think this should be the
only one affected by this. The other integration tests must work
properly since they're run with `assert_cmd_snapshot!`, which I assume
triggers the `SHOULD_COLORIZE` case, and the `cfg!(test)` check will
work for uses in `ruff_linter`.


4a4dc38b5b/crates/ruff_linter/src/message/text.rs (L36-L44)

Alternatively, we could probably move this to a CLI test instead.

Test Plan
--

`cargo test -p ruff`, which was failing on `main` with color codes in
the output before this
2025-07-25 15:47:49 -04:00
UnboundVariable
4a4dc38b5b [ty] Added support for document highlights in playground. (#19540)
This PR adds support for the "document highlights" feature in the ty
playground.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-25 08:55:40 -07:00
Dan Parizher
3e366fdf13 [refurb] Ignore decorated functions for FURB118 (#19339)
## Summary

Fixes #19305
2025-07-25 10:43:17 -05:00
Dhruv Manilawala
53e9e4421c [ty] Add workflow to comment diagnostic diff for conformance tests (#19556)
## Summary

This PR adds a workflow to comment the diff of diagnostics when running
ty between `main` and a pull request on the [typing conformance test
suite](https://github.com/python/typing/tree/main/conformance/tests).

The main workflow is introduced in
https://github.com/astral-sh/ruff/pull/19555 which this workflow depends
on. This workflow is similar to the [mypy primer
comment](d781a6ab3f/.github/workflows/mypy_primer_comment.yaml)
workflow.

## Test Plan

I cannot test this workflow without merging it on `main` unless anyone
knows a way to do this.
2025-07-25 20:54:28 +05:30
Alex Waygood
859262bd49 [ty] Move zope.interface to good.txt for primer runs (#19208) 2025-07-25 14:12:17 +01:00
David Peter
c0768dfd96 [ty] Attribute access on intersections with negative parts (#19524)
## Summary

We currently infer a `@Todo` type whenever we access an attribute on an
intersection type with negative components. This can happen very
naturally. Consequently, this `@Todo` type is rather pervasive and hides
a lot of true positives that ty could otherwise detect:

```py
class Foo:
    attr: int = 1

def _(f: Foo | None):
    if f:
        reveal_type(f)  # Foo & ~AlwaysFalsy

        reveal_type(f.attr)  # now: int, previously: @Todo
```

The changeset here proposes to handle member access on these
intersection types by simply ignoring all negative contributions. This
is not always ideal: a negative contribution like `~<Protocol with
members 'attr'>` could be a hint that `.attr` should not be accessible
on the full intersection type. The behavior can certainly be improved in
the future, but this seems like a reasonable initial step to get rid of
this unnecessary `@Todo` type.

## Ecosystem analysis

There are quite a few changes here. I spot-checked them and found one
bug where attribute access on pure negation types (`~P == object & ~P`)
would not allow attributes on `object` to be accessed. After that was
fixed, I only see true positives and known problems. The fact that a lot
of `unused-ignore-comment` diagnostics go away are also evidence for the
fact that this touches a sensitive area, where static analysis clashes
with dynamically adding attributes to objects:
```py
… # type: ignore # Runtime attribute access
```

## Test Plan

Updated tests.
2025-07-25 14:56:14 +02:00
David Peter
d4eb4277ad [ty] Add basic support for dataclasses.field (#19553)
## Summary

Add basic support for `dataclasses.field`:
* remove fields with `init=False` from the signature of the synthesized
`__init__` method
* infer correct default value types from `default` or `default_factory`
arguments

```py
from dataclasses import dataclass, field

def default_roles() -> list[str]:
    return ["user"]

@dataclass
class Member:
    name: str
    roles: list[str] = field(default_factory=default_roles)
    tag: str | None = field(default=None, init=False)

# revealed: (self: Member, name: str, roles: list[str] = list[str]) -> None
reveal_type(Member.__init__)
```

Support for `kw_only` has **not** been added.

part of https://github.com/astral-sh/ty/issues/111

## Test Plan

New Markdown tests
2025-07-25 14:56:04 +02:00
Micha Reiser
b033fb6bfd [ty] Split ScopedPlaceId into ScopedSymbolId and ScopedMemberId (#19497) 2025-07-25 13:54:33 +02:00
Alex Waygood
f722bfa9e6 [ty] Do not consider a type T to satisfy a method member on a protocol unless the method is available on the meta-type of T (#19187) 2025-07-25 11:16:04 +01:00
Shunsuke Shibayama
b124e182ca [ty] improve lazy scope place lookup (#19321)
Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
Co-authored-by: Carl Meyer <carl@oddbird.net>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-25 07:11:11 +00:00
Dhruv Manilawala
57373a7e4d [ty] Derive Serialize unconditionally on client options (#19549) 2025-07-25 04:03:03 +00:00
Carl Meyer
ae9d450b5f [ty] Fallback to Unknown if no type is stored for an expression (#19517)
## Summary

See discussion at
https://github.com/astral-sh/ruff/pull/19478/files#r2223870292

Fixes https://github.com/astral-sh/ty/issues/865

## Test Plan

Added one mdtest for invalid Callable annotation; removed `pull-types:
skip` from that test file.

Co-authored-by: lipefree <willy.ngo.2000@gmail.com>
2025-07-25 02:05:32 +00:00
UnboundVariable
c8c80e054e [ty] Fix bug #879 in signature help (#19542)
This PR fixes bug [#879](https://github.com/astral-sh/ty/issues/879)
where the signature help popup remains visible after typing the closing
paren in a call expression.

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-24 16:26:14 -07:00
UnboundVariable
4bc34b82ef [ty] Added support for "document highlights" language server feature. (#19515)
This PR adds support for the "document highlights" language server
feature.

This feature allows a client to highlight all instances of a selected
name within a document. Without this feature, editors perform
highlighting based on a simple text match. This adds semantic knowledge.

The implementation of this feature largely overlaps that of the
recently-added "references" feature. This PR refactors the existing
"references.rs" module, separating out the functionality and tests that
are specific to the other language feature into a "goto_references.rs"
module. The "references.rs" module now contains the functionality that
is common to "goto references", "document highlights" and "rename"
(which is not yet implemented).

As part of this PR, I also created a new `ReferenceTarget` type which is
similar to the existing `NavigationTarget` type but better suited for
references. This idea was suggested by @MichaReiser in [this code review
feedback](https://github.com/astral-sh/ruff/pull/19475#discussion_r2224061006)
from a previous PR. Notably, this new type contains a field that
specifies the "kind" of the reference (read, write or other). This
"kind" is needed for the document highlights feature.

Before: all textual instances of `foo` are highlighted
<img width="156" height="126" alt="Screenshot 2025-07-23 at 12 51 09 PM"
src="https://github.com/user-attachments/assets/37ccdb2f-d48a-473d-89d5-8e89cb6c394e"
/>

After: only semantic matches are highlighted
<img width="164" height="157" alt="Screenshot 2025-07-23 at 12 52 05 PM"
src="https://github.com/user-attachments/assets/2efadadd-4691-4815-af04-b031e74c81b7"
/>

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-24 13:06:25 -07:00
Charlie Marsh
d9cab4d242 Add support for specifying minimum dots in detected string imports (#19538)
## Summary

Defaults to requiring two dots, which matches the Pants default.
2025-07-24 15:48:23 -04:00
David Peter
d77b7312b0 [ty] Minor: fix incomplete docstring (#19534) 2025-07-24 21:01:15 +02:00
Dhruv Manilawala
f9091ea8bb [ty] Move server tests as integration tests (#19522)
## Summary

Reference:
https://github.com/astral-sh/ruff/pull/19391#discussion_r2222780892
2025-07-24 16:10:17 +00:00
Robsdedude
1d2181623c [ruff] Offer fixes for RUF039 in more cases (#19065)
## Summary
Expand cases in which ruff can offer a fix for `RUF039` (some of which
are unsafe).

While turning `"\n"` (== `\n`) into `r"\n"` (== `\\n`) is not equivalent
at run-time, it's still functionally equivalent to do so in the context
of [regex
patterns](https://docs.python.org/3/library/re.html#regular-expression-syntax)
as they themselves interpret the escape sequence. Therefore, an unsafe
fix can be offered.

Further, this PR also makes ruff offer fixes for byte string literals,
not only strings literals as before.

## Test Plan
Tests for all escape sequences have been added.

## Related
Closes: https://github.com/astral-sh/ruff/issues/16713

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-24 11:45:45 -04:00
David Peter
dc6be457b5 [ty] Support dataclasses.InitVar (#19527)
## Summary

I saw that this creates a lot of false positives in the ecosystem, and
it seemed to be relatively easy to add basic support for this.

Some preliminary work on this was done by @InSyncWithFoo — thank you.

part of https://github.com/astral-sh/ty/issues/111

## Ecosystem analysis

The results look good.

## Test Plan

New Markdown tests

---------

Co-authored-by: InSync <insyncwithfoo@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-24 16:33:33 +02:00
Robsdedude
1079975b35 [ruff] Fix RUF033 breaking with named default expressions (#19115)
## Summary
The generated fix for `RUF033` would cause a syntax error for named
expressions as parameter defaults.
```python
from dataclasses import InitVar, dataclass
@dataclass
class Foo:
    def __post_init__(self, bar: int = (x := 1)) -> None:
        pass
```
would be turned into
```python
from dataclasses import InitVar, dataclass
@dataclass
class Foo:
    x: InitVar[int] = x := 1
    def __post_init__(self, bar: int = (x := 1)) -> None:
        pass
```
instead of the syntactically correct
```python
# ...
x: InitVar[int] = (x := 1)
# ...
```

## Test Plan
Test reproducer (plus some extra tests) have been added to the test
suite.

## Related
Fixes: https://github.com/astral-sh/ruff/issues/18950
2025-07-24 09:45:49 -04:00
Brent Westbrook
39eb0f6c6c Update pre-commit hook name (#19530)
## Summary

A couple of months ago now
(https://github.com/astral-sh/ruff-pre-commit/pull/124) we changed the
hook ID from just `ruff` to `ruff-check` to mirror `ruff-format`. I
noticed the `ruff (legacy alias)` when running pre-commit on the release
today and realized we should probably update.

## Test Plan

Commit on this PR:

```shell
> git commit -m "Update pre-commit hook name"
check for merge conflicts................................................Passed
Validate pyproject.toml..............................(no files to check)Skipped
mdformat.............................................(no files to check)Skipped
markdownlint-fix.....................................(no files to check)Skipped
blacken-docs.........................................(no files to check)Skipped
typos....................................................................Passed
cargo fmt............................................(no files to check)Skipped
ruff format..........................................(no files to check)Skipped
ruff check...........................................(no files to check)Skipped  <-- 
prettier.................................................................Passed
zizmor...............................................(no files to check)Skipped
Validate GitHub Workflows............................(no files to check)Skipped
shellcheck...........................................(no files to check)Skipped
```

Compared to the release branch:

```shell
> pre-commit run
...
cargo fmt............................................(no files to check)Skipped
ruff format..........................................(no files to check)Skipped
ruff (legacy alias)..................................(no files to check)Skipped
...
```
2025-07-24 09:44:47 -04:00
208 changed files with 13438 additions and 9604 deletions

View File

@@ -0,0 +1,97 @@
name: PR comment (typing_conformance)
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: [Run typing conformance]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The typing_conformance workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download PR number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download typing_conformance results"
id: download-typing_conformance_diff
if: steps.pr-number.outputs.pr-number
with:
name: typing_conformance_diagnostics_diff
workflow: typing_conformance.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/typing_conformance_diagnostics_diff
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: ${{ steps.download-typing_conformance_diff.outputs.found_artifact == 'true' }}
run: |
# Guard against malicious typing_conformance results that symlink to a secret
# file on this runner
if [[ -L pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff ]]
then
echo "Error: typing_conformance_diagnostics.diff cannot be a symlink"
exit 1
fi
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment typing_conformance_diagnostics_diff -->' >> comment.txt
echo '## Diagnostic diff on typing conformance tests' >> comment.txt
if [ -s "pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff" ]; then
echo '<details>' >> comment.txt
echo '<summary>Changes were detected when running ty on typing conformance tests</summary>' >> comment.txt
echo '' >> comment.txt
echo '```diff' >> comment.txt
cat pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff >> comment.txt
echo '```' >> comment.txt
echo '</details>' >> comment.txt
else
echo 'No changes detected when running ty on typing conformance tests ✅' >> comment.txt
fi
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.txt >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment typing_conformance_diagnostics_diff -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace

View File

@@ -84,7 +84,7 @@ repos:
rev: v0.12.4
hooks:
- id: ruff-format
- id: ruff
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi]
require_serial: true

2
Cargo.lock generated
View File

@@ -2960,6 +2960,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"memchr",
"ruff_cache",
"ruff_db",
"ruff_linter",
@@ -4304,7 +4305,6 @@ dependencies = [
"strum_macros",
"tempfile",
"test-case",
"thin-vec",
"thiserror 2.0.12",
"tracing",
"ty_python_semantic",

View File

@@ -166,7 +166,6 @@ strum_macros = { version = "0.27.0" }
syn = { version = "2.0.55" }
tempfile = { version = "3.9.0" }
test-case = { version = "3.3.1" }
thin-vec = { version = "0.2.14" }
thiserror = { version = "2.0.0" }
tikv-jemallocator = { version = "0.6.0" }
toml = { version = "0.9.0" }

View File

@@ -169,6 +169,9 @@ pub struct AnalyzeGraphCommand {
/// Attempt to detect imports from string literals.
#[clap(long)]
detect_string_imports: bool,
/// The minimum number of dots in a string import to consider it a valid import.
#[clap(long)]
min_dots: Option<usize>,
/// Enable preview mode. Use `--no-preview` to disable.
#[arg(long, overrides_with("no_preview"))]
preview: bool,
@@ -808,6 +811,7 @@ impl AnalyzeGraphCommand {
} else {
None
},
string_imports_min_dots: self.min_dots,
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
target_version: self.target_version.map(ast::PythonVersion::from),
..ExplicitConfigOverrides::default()
@@ -1305,6 +1309,7 @@ struct ExplicitConfigOverrides {
show_fixes: Option<bool>,
extension: Option<Vec<ExtensionPair>>,
detect_string_imports: Option<bool>,
string_imports_min_dots: Option<usize>,
}
impl ConfigurationTransformer for ExplicitConfigOverrides {
@@ -1392,6 +1397,9 @@ impl ConfigurationTransformer for ExplicitConfigOverrides {
if let Some(detect_string_imports) = &self.detect_string_imports {
config.analyze.detect_string_imports = Some(*detect_string_imports);
}
if let Some(string_imports_min_dots) = &self.string_imports_min_dots {
config.analyze.string_imports_min_dots = Some(*string_imports_min_dots);
}
config
}

View File

@@ -102,7 +102,7 @@ pub(crate) fn analyze_graph(
// Resolve the per-file settings.
let settings = resolver.resolve(path);
let string_imports = settings.analyze.detect_string_imports;
let string_imports = settings.analyze.string_imports;
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();
// Skip excluded files.

View File

@@ -279,6 +279,7 @@ mod test {
TextEmitter::default()
.with_show_fix_status(true)
.with_color(false)
.emit(
&mut output,
&diagnostics.inner,

View File

@@ -197,23 +197,43 @@ fn string_detection() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("--detect-string-imports").current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [
"ruff/c.py"
],
"ruff/c.py": []
}
assert_cmd_snapshot!(command().arg("--detect-string-imports").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [],
"ruff/c.py": []
}
----- stderr -----
"###);
----- stderr -----
"#);
});
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("--detect-string-imports").arg("--min-dots").arg("1").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [
"ruff/c.py"
],
"ruff/c.py": []
}
----- stderr -----
"#);
});
Ok(())

View File

@@ -2422,7 +2422,7 @@ requires-python = ">= 3.11"
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.11
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
@@ -2734,7 +2734,7 @@ requires-python = ">= 3.11"
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.10
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
@@ -3098,7 +3098,7 @@ from typing import Union;foo: Union[int, str] = 1
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.11
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
@@ -3478,7 +3478,7 @@ from typing import Union;foo: Union[int, str] = 1
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.11
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
@@ -3806,7 +3806,7 @@ from typing import Union;foo: Union[int, str] = 1
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.10
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
@@ -4134,7 +4134,7 @@ from typing import Union;foo: Union[int, str] = 1
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.9
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
@@ -4419,7 +4419,7 @@ from typing import Union;foo: Union[int, str] = 1
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.9
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
@@ -4757,7 +4757,7 @@ from typing import Union;foo: Union[int, str] = 1
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.10
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}

View File

@@ -392,7 +392,7 @@ formatter.docstring_code_line_width = dynamic
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.7
analyze.detect_string_imports = false
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}

View File

@@ -20,6 +20,7 @@ ty_python_semantic = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, optional = true }
memchr = { workspace = true }
salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }

View File

@@ -1,3 +1,4 @@
use crate::StringImports;
use ruff_python_ast::visitor::source_order::{
SourceOrderVisitor, walk_expr, walk_module, walk_stmt,
};
@@ -10,13 +11,13 @@ pub(crate) struct Collector<'a> {
/// The path to the current module.
module_path: Option<&'a [String]>,
/// Whether to detect imports from string literals.
string_imports: bool,
string_imports: StringImports,
/// The collected imports from the Python AST.
imports: Vec<CollectedImport>,
}
impl<'a> Collector<'a> {
pub(crate) fn new(module_path: Option<&'a [String]>, string_imports: bool) -> Self {
pub(crate) fn new(module_path: Option<&'a [String]>, string_imports: StringImports) -> Self {
Self {
module_path,
string_imports,
@@ -118,7 +119,7 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
| Stmt::Continue(_)
| Stmt::IpyEscapeCommand(_) => {
// Only traverse simple statements when string imports is enabled.
if self.string_imports {
if self.string_imports.enabled {
walk_stmt(self, stmt);
}
}
@@ -126,20 +127,26 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
}
fn visit_expr(&mut self, expr: &'ast Expr) {
if self.string_imports {
if self.string_imports.enabled {
if let Expr::StringLiteral(ast::ExprStringLiteral {
value,
range: _,
node_index: _,
}) = expr
{
// Determine whether the string literal "looks like" an import statement: contains
// a dot, and consists solely of valid Python identifiers.
let value = value.to_str();
if let Some(module_name) = ModuleName::new(value) {
self.imports.push(CollectedImport::Import(module_name));
// Determine whether the string literal "looks like" an import statement: contains
// the requisite number of dots, and consists solely of valid Python identifiers.
if self.string_imports.min_dots == 0
|| memchr::memchr_iter(b'.', value.as_bytes()).count()
>= self.string_imports.min_dots
{
if let Some(module_name) = ModuleName::new(value) {
self.imports.push(CollectedImport::Import(module_name));
}
}
}
walk_expr(self, expr);
}
}

View File

@@ -9,7 +9,7 @@ use ruff_python_parser::{Mode, ParseOptions, parse};
use crate::collector::Collector;
pub use crate::db::ModuleDb;
use crate::resolver::Resolver;
pub use crate::settings::{AnalyzeSettings, Direction};
pub use crate::settings::{AnalyzeSettings, Direction, StringImports};
mod collector;
mod db;
@@ -26,7 +26,7 @@ impl ModuleImports {
db: &ModuleDb,
path: &SystemPath,
package: Option<&SystemPath>,
string_imports: bool,
string_imports: StringImports,
) -> Result<Self> {
// Read and parse the source code.
let source = std::fs::read_to_string(path)?;

View File

@@ -11,7 +11,7 @@ pub struct AnalyzeSettings {
pub exclude: FilePatternSet,
pub preview: PreviewMode,
pub target_version: PythonVersion,
pub detect_string_imports: bool,
pub string_imports: StringImports,
pub include_dependencies: BTreeMap<PathBuf, (PathBuf, Vec<String>)>,
pub extension: ExtensionMapping,
}
@@ -26,7 +26,7 @@ impl fmt::Display for AnalyzeSettings {
self.exclude,
self.preview,
self.target_version,
self.detect_string_imports,
self.string_imports,
self.extension | debug,
self.include_dependencies | debug,
]
@@ -35,6 +35,31 @@ impl fmt::Display for AnalyzeSettings {
}
}
#[derive(Debug, Copy, Clone, CacheKey)]
pub struct StringImports {
pub enabled: bool,
pub min_dots: usize,
}
impl Default for StringImports {
fn default() -> Self {
Self {
enabled: false,
min_dots: 2,
}
}
}
impl fmt::Display for StringImports {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.enabled {
write!(f, "enabled (min_dots: {})", self.min_dots)
} else {
write!(f, "disabled")
}
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, CacheKey)]
#[cfg_attr(
feature = "serde",

View File

@@ -25,5 +25,5 @@ def my_func():
# t-strings - all ok
t"0.0.0.0"
"0.0.0.0" t"0.0.0.0{expr}0.0.0.0"
"0.0.0.0" f"0.0.0.0{expr}0.0.0.0" t"0.0.0.0{expr}0.0.0.0"
t"0.0.0.0" t"0.0.0.0{expr}0.0.0.0"
t"0.0.0.0" t"0.0.0.0{expr}0.0.0.0" t"0.0.0.0{expr}0.0.0.0"

View File

@@ -94,7 +94,7 @@ except Exception:
logging.error("...", exc_info=True)
from logging import error, exception
from logging import critical, error, exception
try:
pass
@@ -114,6 +114,23 @@ except Exception:
error("...", exc_info=None)
try:
pass
except Exception:
critical("...")
try:
pass
except Exception:
critical("...", exc_info=False)
try:
pass
except Exception:
critical("...", exc_info=None)
try:
pass
except Exception:
@@ -125,6 +142,13 @@ try:
except Exception:
error("...", exc_info=True)
try:
pass
except Exception:
critical("...", exc_info=True)
try:
...
except Exception as e:

View File

@@ -143,3 +143,23 @@ class NotAMethodButHardToDetect:
# without risking false positives elsewhere or introducing complex heuristics
# that users would find surprising and confusing
FOO = sorted([x for x in BAR], key=lambda x: x.baz)
# https://github.com/astral-sh/ruff/issues/19305
import pytest
@pytest.fixture
def my_fixture_with_param(request):
return request.param
@pytest.fixture()
def my_fixture_with_param2(request):
return request.param
# Decorated function (should be ignored)
def custom_decorator(func):
return func
@custom_decorator
def add(x, y):
return x + y

View File

@@ -65,3 +65,62 @@ class Foo:
bar = "should've used attrs"
def __post_init__(self, bar: str = "ahhh", baz: str = "hmm") -> None: ...
# https://github.com/astral-sh/ruff/issues/18950
@dataclass
class Foo:
def __post_init__(self, bar: int = (x := 1)) -> None:
pass
@dataclass
class Foo:
def __post_init__(
self,
bar: int = (x := 1) # comment
,
baz: int = (y := 2), # comment
foo = (a := 1) # comment
,
faz = (b := 2), # comment
) -> None:
pass
@dataclass
class Foo:
def __post_init__(
self,
bar: int = 1, # comment
baz: int = 2, # comment
) -> None:
pass
@dataclass
class Foo:
def __post_init__(
self,
arg1: int = (1) # comment
,
arg2: int = ((1)) # comment
,
arg2: int = (i for i in range(10)) # comment
,
) -> None:
pass
# makes little sense, but is valid syntax
def fun_with_python_syntax():
@dataclass
class Foo:
def __post_init__(
self,
bar: (int) = (yield from range(5)) # comment
,
) -> None:
...
return Foo

View File

@@ -53,3 +53,16 @@ regex.subn(br"""eak your machine with rm -""", rf"""/""")
regex.splititer(both, non_literal)
regex.subf(f, lambda _: r'means', '"format"')
regex.subfn(fn, f'''a$1n't''', lambda: "'function'")
# https://github.com/astral-sh/ruff/issues/16713
re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
re.compile("\b") # without fix
re.compile("\"") # without fix
re.compile("\'") # without fix
re.compile('\"') # without fix
re.compile('\'') # without fix
re.compile("\\") # without fix
re.compile("\101") # without fix
re.compile("a\
b") # without fix

View File

@@ -91,3 +91,20 @@ regex.subf(
br''br""br''
)
regex.subfn(br'I\s\nee*d\s[O0o]me\x20\Qoffe\E, ' br'b')
# https://github.com/astral-sh/ruff/issues/16713
re.compile(
"["
"\U0001F600-\U0001F64F" # emoticons
"\U0001F300-\U0001F5FF" # symbols & pictographs
"\U0001F680-\U0001F6FF" # transport & map symbols
"\U0001F1E0-\U0001F1FF" # flags (iOS)
"\U00002702-\U000027B0"
"\U000024C2-\U0001F251"
"\u200d" # zero width joiner
"\u200c" # zero width non-joiner
"\\u200c" # must not be escaped in a raw string
"]+",
flags=re.UNICODE,
)

View File

@@ -0,0 +1,3 @@
import re
re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix

View File

@@ -473,7 +473,7 @@ pub fn lint_only(
&& !is_py314_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.14 is under development and may be unstable. Enable `preview` to remove this warning."
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
);
}
@@ -584,7 +584,7 @@ pub fn lint_fix<'a>(
&& !is_py314_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.14 is under development and may be unstable. Enable `preview` to remove this warning."
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
);
}

View File

@@ -76,6 +76,12 @@ impl TextEmitter {
self.config = self.config.preview(preview);
self
}
#[must_use]
pub fn with_color(mut self, color: bool) -> Self {
self.config = self.config.color(color);
self
}
}
impl Emitter for TextEmitter {

View File

@@ -47,9 +47,10 @@ use crate::checkers::ast::Checker;
/// raise
/// ```
///
/// Exceptions that are logged via `logging.exception()` or `logging.error()`
/// with `exc_info` enabled will _not_ be flagged, as this is a common pattern
/// for propagating exception traces:
/// Exceptions that are logged via `logging.exception()` or are logged via
/// `logging.error()` or `logging.critical()` with `exc_info` enabled will
/// _not_ be flagged, as this is a common pattern for propagating exception
/// traces:
/// ```python
/// try:
/// foo()
@@ -201,7 +202,7 @@ impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> {
) {
if match attr.as_str() {
"exception" => true,
"error" => arguments
"error" | "critical" => arguments
.find_keyword("exc_info")
.is_some_and(|keyword| is_const_true(&keyword.value)),
_ => false,
@@ -214,7 +215,7 @@ impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> {
if self.semantic.resolve_qualified_name(func).is_some_and(
|qualified_name| match qualified_name.segments() {
["logging", "exception"] => true,
["logging", "error"] => arguments
["logging", "error" | "critical"] => arguments
.find_keyword("exc_info")
.is_some_and(|keyword| is_const_true(&keyword.value)),
_ => false,

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/flake8_blind_except/mod.rs
snapshot_kind: text
---
BLE.py:25:8: BLE001 Do not catch blind exception: `BaseException`
|
@@ -121,3 +120,30 @@ BLE.py:113:8: BLE001 Do not catch blind exception: `Exception`
| ^^^^^^^^^ BLE001
114 | error("...", exc_info=None)
|
BLE.py:119:8: BLE001 Do not catch blind exception: `Exception`
|
117 | try:
118 | pass
119 | except Exception:
| ^^^^^^^^^ BLE001
120 | critical("...")
|
BLE.py:125:8: BLE001 Do not catch blind exception: `Exception`
|
123 | try:
124 | pass
125 | except Exception:
| ^^^^^^^^^ BLE001
126 | critical("...", exc_info=False)
|
BLE.py:131:8: BLE001 Do not catch blind exception: `Exception`
|
129 | try:
130 | pass
131 | except Exception:
| ^^^^^^^^^ BLE001
132 | critical("...", exc_info=None)
|

View File

@@ -104,6 +104,13 @@ pub(crate) fn reimplemented_operator(checker: &Checker, target: &FunctionLike) {
return;
}
// Skip decorated functions
if let FunctionLike::Function(func) = target {
if !func.decorator_list.is_empty() {
return;
}
}
let Some(params) = target.parameters() else {
return;
};

View File

@@ -555,6 +555,44 @@ mod tests {
Ok(())
}
#[test_case(Rule::UnrawRePattern, Path::new("RUF039_py_version_sensitive.py"))]
fn preview_rules_py37(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__py37__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("ruff").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
unresolved_target_version: PythonVersion::PY37.into(),
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UnrawRePattern, Path::new("RUF039_py_version_sensitive.py"))]
fn preview_rules_py38(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__py38__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("ruff").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
unresolved_target_version: PythonVersion::PY38.into(),
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"^_+", 1)]
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"", 2)]
fn custom_regexp_preset(

View File

@@ -2,6 +2,7 @@ use anyhow::Context;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_semantic::{Scope, ScopeKind};
use ruff_python_trivia::{indentation_at_offset, textwrap};
use ruff_source_file::LineRanges;
@@ -117,13 +118,7 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
if !stopped_fixes {
diagnostic.try_set_fix(|| {
use_initvar(
current_scope,
function_def,
&parameter.parameter,
default,
checker,
)
use_initvar(current_scope, function_def, parameter, default, checker)
});
// Need to stop fixes as soon as there is a parameter we cannot fix.
// Otherwise, we risk a syntax error (a parameter without a default
@@ -138,10 +133,11 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
fn use_initvar(
current_scope: &Scope,
post_init_def: &ast::StmtFunctionDef,
parameter: &ast::Parameter,
parameter_with_default: &ast::ParameterWithDefault,
default: &ast::Expr,
checker: &Checker,
) -> anyhow::Result<Fix> {
let parameter = &parameter_with_default.parameter;
if current_scope.has(&parameter.name) {
return Err(anyhow::anyhow!(
"Cannot add a `{}: InitVar` field to the class body, as a field by that name already exists",
@@ -157,17 +153,25 @@ fn use_initvar(
checker.semantic(),
)?;
let locator = checker.locator();
let default_loc = parenthesized_range(
default.into(),
parameter_with_default.into(),
checker.comment_ranges(),
checker.source(),
)
.unwrap_or(default.range());
// Delete the default value. For example,
// - def __post_init__(self, foo: int = 0) -> None: ...
// + def __post_init__(self, foo: int) -> None: ...
let default_edit = Edit::deletion(parameter.end(), default.end());
let default_edit = Edit::deletion(parameter.end(), default_loc.end());
// Add `dataclasses.InitVar` field to class body.
let locator = checker.locator();
let content = {
let default = locator.slice(default_loc);
let parameter_name = locator.slice(&parameter.name);
let default = locator.slice(default);
let line_ending = checker.stylist().line_ending().as_str();
if let Some(annotation) = &parameter

View File

@@ -1,13 +1,13 @@
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{
BytesLiteral, Expr, ExprBytesLiteral, ExprCall, ExprStringLiteral, StringLiteral,
BytesLiteral, Expr, ExprBytesLiteral, ExprCall, ExprStringLiteral, PythonVersion, StringLiteral,
};
use ruff_python_semantic::{Modules, SemanticModel};
use ruff_text_size::Ranged;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::{Edit, Fix, FixAvailability, Violation};
@@ -24,6 +24,29 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// Regular expressions should be written
/// using raw strings to avoid double escaping.
///
/// ## Fix safety
/// The fix is unsafe if the string/bytes literal contains an escape sequence because the fix alters
/// the runtime value of the literal while retaining the regex semantics.
///
/// For example
/// ```python
/// # Literal is `1\n2`.
/// re.compile("1\n2")
///
/// # Literal is `1\\n2`, but the regex library will interpret `\\n` and will still match a newline
/// # character as before.
/// re.compile(r"1\n2")
/// ```
///
/// ## Fix availability
/// A fix is not available if either
/// * the argument is a string with a (no-op) `u` prefix (e.g., `u"foo"`) as the prefix is
/// incompatible with the raw prefix `r`
/// * the argument is a string or bytes literal with an escape sequence that has a different
/// meaning in the context of a regular expression such as `\b`, which is word boundary or
/// backspace in a regex, depending on the context, but always a backspace in string and bytes
/// literals.
///
/// ## Example
///
/// ```python
@@ -163,20 +186,44 @@ fn check_string(checker: &Checker, literal: &StringLiteral, module: RegexModule,
let range = literal.range;
let mut diagnostic = checker.report_diagnostic(UnrawRePattern { module, func, kind }, range);
if
// The (no-op) `u` prefix is a syntax error when combined with `r`
!literal.flags.prefix().is_unicode()
// We are looking for backslash characters
// in the raw source code here, because `\n`
// gets converted to a single character already
// at the lexing stage.
&&!checker.locator().slice(literal.range()).contains('\\')
{
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
"r".to_string(),
literal.range().start(),
)));
let Some(applicability) = raw_string_applicability(checker, literal) else {
return;
};
diagnostic.set_fix(Fix::applicable_edit(
Edit::insertion("r".to_string(), literal.range().start()),
applicability,
));
}
/// Check how safe it is to prepend the `r` prefix to the string.
///
/// ## Returns
/// * `None` if the prefix cannot be added,
/// * `Some(a)` if it can be added with applicability `a`.
fn raw_string_applicability(checker: &Checker, literal: &StringLiteral) -> Option<Applicability> {
if literal.flags.prefix().is_unicode() {
// The (no-op) `u` prefix is a syntax error when combined with `r`
return None;
}
if checker.target_version() >= PythonVersion::PY38 {
raw_applicability(checker, literal.range(), |escaped| {
matches!(
escaped,
Some('a' | 'f' | 'n' | 'r' | 't' | 'u' | 'U' | 'v' | 'x' | 'N')
)
})
} else {
raw_applicability(checker, literal.range(), |escaped| {
matches!(
escaped,
Some('a' | 'f' | 'n' | 'r' | 't' | 'u' | 'U' | 'v' | 'x')
)
})
}
// re.compile("\a\f\n\N{Partial Differential}\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
}
fn check_bytes(checker: &Checker, literal: &BytesLiteral, module: RegexModule, func: &str) {
@@ -187,5 +234,53 @@ fn check_bytes(checker: &Checker, literal: &BytesLiteral, module: RegexModule, f
let kind = PatternKind::Bytes;
let func = func.to_string();
let range = literal.range;
checker.report_diagnostic(UnrawRePattern { module, func, kind }, range);
let mut diagnostic = checker.report_diagnostic(UnrawRePattern { module, func, kind }, range);
let Some(applicability) = raw_byte_applicability(checker, literal) else {
return;
};
diagnostic.set_fix(Fix::applicable_edit(
Edit::insertion("r".to_string(), literal.range().start()),
applicability,
));
}
/// Check how same it is to prepend the `r` prefix to the byte sting.
///
/// ## Returns
/// * `None` if the prefix cannot be added,
/// * `Some(a)` if it can be added with applicability `a`.
fn raw_byte_applicability(checker: &Checker, literal: &BytesLiteral) -> Option<Applicability> {
raw_applicability(checker, literal.range(), |escaped| {
matches!(escaped, Some('a' | 'f' | 'n' | 'r' | 't' | 'v' | 'x'))
})
}
fn raw_applicability(
checker: &Checker,
literal_range: TextRange,
match_allowed_escape_sequence: impl Fn(Option<char>) -> bool,
) -> Option<Applicability> {
let mut found_slash = false;
let mut chars = checker.locator().slice(literal_range).chars().peekable();
while let Some(char) = chars.next() {
if char == '\\' {
found_slash = true;
// Turning `"\uXXXX"` into `r"\uXXXX"` is behaviorally equivalent when passed
// to `re`, however, it's not exactly the same runtime value.
// Similarly, for the other escape sequences.
if !match_allowed_escape_sequence(chars.peek().copied()) {
// If the next character is not one of the whitelisted ones, we likely cannot safely turn
// this into a raw string.
return None;
}
}
}
Some(if found_slash {
Applicability::Unsafe
} else {
Applicability::Safe
})
}

View File

@@ -156,3 +156,281 @@ RUF033.py:67:59: RUF033 `__post_init__` method with argument defaults
| ^^^^^ RUF033
|
= help: Use `dataclasses.InitVar` instead
RUF033.py:73:41: RUF033 [*] `__post_init__` method with argument defaults
|
71 | @dataclass
72 | class Foo:
73 | def __post_init__(self, bar: int = (x := 1)) -> None:
| ^^^^^^ RUF033
74 | pass
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
70 70 | # https://github.com/astral-sh/ruff/issues/18950
71 71 | @dataclass
72 72 | class Foo:
73 |- def __post_init__(self, bar: int = (x := 1)) -> None:
73 |+ bar: InitVar[int] = (x := 1)
74 |+ def __post_init__(self, bar: int) -> None:
74 75 | pass
75 76 |
76 77 |
RUF033.py:81:21: RUF033 [*] `__post_init__` method with argument defaults
|
79 | def __post_init__(
80 | self,
81 | bar: int = (x := 1) # comment
| ^^^^^^ RUF033
82 | ,
83 | baz: int = (y := 2), # comment
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
76 76 |
77 77 | @dataclass
78 78 | class Foo:
79 |+ bar: InitVar[int] = (x := 1)
79 80 | def __post_init__(
80 81 | self,
81 |- bar: int = (x := 1) # comment
82 |+ bar: int # comment
82 83 | ,
83 84 | baz: int = (y := 2), # comment
84 85 | foo = (a := 1) # comment
RUF033.py:83:21: RUF033 [*] `__post_init__` method with argument defaults
|
81 | bar: int = (x := 1) # comment
82 | ,
83 | baz: int = (y := 2), # comment
| ^^^^^^ RUF033
84 | foo = (a := 1) # comment
85 | ,
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
76 76 |
77 77 | @dataclass
78 78 | class Foo:
79 |+ baz: InitVar[int] = (y := 2)
79 80 | def __post_init__(
80 81 | self,
81 82 | bar: int = (x := 1) # comment
82 83 | ,
83 |- baz: int = (y := 2), # comment
84 |+ baz: int, # comment
84 85 | foo = (a := 1) # comment
85 86 | ,
86 87 | faz = (b := 2), # comment
RUF033.py:84:16: RUF033 [*] `__post_init__` method with argument defaults
|
82 | ,
83 | baz: int = (y := 2), # comment
84 | foo = (a := 1) # comment
| ^^^^^^ RUF033
85 | ,
86 | faz = (b := 2), # comment
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
76 76 |
77 77 | @dataclass
78 78 | class Foo:
79 |+ foo: InitVar = (a := 1)
79 80 | def __post_init__(
80 81 | self,
81 82 | bar: int = (x := 1) # comment
82 83 | ,
83 84 | baz: int = (y := 2), # comment
84 |- foo = (a := 1) # comment
85 |+ foo # comment
85 86 | ,
86 87 | faz = (b := 2), # comment
87 88 | ) -> None:
RUF033.py:86:16: RUF033 [*] `__post_init__` method with argument defaults
|
84 | foo = (a := 1) # comment
85 | ,
86 | faz = (b := 2), # comment
| ^^^^^^ RUF033
87 | ) -> None:
88 | pass
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
76 76 |
77 77 | @dataclass
78 78 | class Foo:
79 |+ faz: InitVar = (b := 2)
79 80 | def __post_init__(
80 81 | self,
81 82 | bar: int = (x := 1) # comment
--------------------------------------------------------------------------------
83 84 | baz: int = (y := 2), # comment
84 85 | foo = (a := 1) # comment
85 86 | ,
86 |- faz = (b := 2), # comment
87 |+ faz, # comment
87 88 | ) -> None:
88 89 | pass
89 90 |
RUF033.py:95:20: RUF033 [*] `__post_init__` method with argument defaults
|
93 | def __post_init__(
94 | self,
95 | bar: int = 1, # comment
| ^ RUF033
96 | baz: int = 2, # comment
97 | ) -> None:
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
90 90 |
91 91 | @dataclass
92 92 | class Foo:
93 |+ bar: InitVar[int] = 1
93 94 | def __post_init__(
94 95 | self,
95 |- bar: int = 1, # comment
96 |+ bar: int, # comment
96 97 | baz: int = 2, # comment
97 98 | ) -> None:
98 99 | pass
RUF033.py:96:20: RUF033 [*] `__post_init__` method with argument defaults
|
94 | self,
95 | bar: int = 1, # comment
96 | baz: int = 2, # comment
| ^ RUF033
97 | ) -> None:
98 | pass
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
90 90 |
91 91 | @dataclass
92 92 | class Foo:
93 |+ baz: InitVar[int] = 2
93 94 | def __post_init__(
94 95 | self,
95 96 | bar: int = 1, # comment
96 |- baz: int = 2, # comment
97 |+ baz: int, # comment
97 98 | ) -> None:
98 99 | pass
99 100 |
RUF033.py:105:22: RUF033 [*] `__post_init__` method with argument defaults
|
103 | def __post_init__(
104 | self,
105 | arg1: int = (1) # comment
| ^ RUF033
106 | ,
107 | arg2: int = ((1)) # comment
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
100 100 |
101 101 | @dataclass
102 102 | class Foo:
103 |+ arg1: InitVar[int] = (1)
103 104 | def __post_init__(
104 105 | self,
105 |- arg1: int = (1) # comment
106 |+ arg1: int # comment
106 107 | ,
107 108 | arg2: int = ((1)) # comment
108 109 | ,
RUF033.py:107:23: RUF033 [*] `__post_init__` method with argument defaults
|
105 | arg1: int = (1) # comment
106 | ,
107 | arg2: int = ((1)) # comment
| ^ RUF033
108 | ,
109 | arg2: int = (i for i in range(10)) # comment
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
100 100 |
101 101 | @dataclass
102 102 | class Foo:
103 |+ arg2: InitVar[int] = ((1))
103 104 | def __post_init__(
104 105 | self,
105 106 | arg1: int = (1) # comment
106 107 | ,
107 |- arg2: int = ((1)) # comment
108 |+ arg2: int # comment
108 109 | ,
109 110 | arg2: int = (i for i in range(10)) # comment
110 111 | ,
RUF033.py:109:21: RUF033 [*] `__post_init__` method with argument defaults
|
107 | arg2: int = ((1)) # comment
108 | ,
109 | arg2: int = (i for i in range(10)) # comment
| ^^^^^^^^^^^^^^^^^^^^^^ RUF033
110 | ,
111 | ) -> None:
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
100 100 |
101 101 | @dataclass
102 102 | class Foo:
103 |+ arg2: InitVar[int] = (i for i in range(10))
103 104 | def __post_init__(
104 105 | self,
105 106 | arg1: int = (1) # comment
106 107 | ,
107 108 | arg2: int = ((1)) # comment
108 109 | ,
109 |- arg2: int = (i for i in range(10)) # comment
110 |+ arg2: int # comment
110 111 | ,
111 112 | ) -> None:
112 113 | pass
RUF033.py:121:27: RUF033 [*] `__post_init__` method with argument defaults
|
119 | def __post_init__(
120 | self,
121 | bar: (int) = (yield from range(5)) # comment
| ^^^^^^^^^^^^^^^^^^^ RUF033
122 | ,
123 | ) -> None:
|
= help: Use `dataclasses.InitVar` instead
Unsafe fix
116 116 | def fun_with_python_syntax():
117 117 | @dataclass
118 118 | class Foo:
119 |+ bar: InitVar[int] = (yield from range(5))
119 120 | def __post_init__(
120 121 | self,
121 |- bar: (int) = (yield from range(5)) # comment
122 |+ bar: (int) # comment
122 123 | ,
123 124 | ) -> None:
124 125 | ...

View File

@@ -21,7 +21,7 @@ RUF039.py:5:12: RUF039 [*] First argument to `re.compile()` is not raw string
7 7 | re.finditer("dou\ble")
8 8 | re.fullmatch('''t\riple single''')
RUF039.py:6:12: RUF039 First argument to `re.findall()` is not raw string
RUF039.py:6:12: RUF039 [*] First argument to `re.findall()` is not raw string
|
4 | # Errors
5 | re.compile('single free-spacing', flags=re.X)
@@ -32,6 +32,16 @@ RUF039.py:6:12: RUF039 First argument to `re.findall()` is not raw string
|
= help: Replace with raw string
Unsafe fix
3 3 |
4 4 | # Errors
5 5 | re.compile('single free-spacing', flags=re.X)
6 |-re.findall('si\ngle')
6 |+re.findall(r'si\ngle')
7 7 | re.finditer("dou\ble")
8 8 | re.fullmatch('''t\riple single''')
9 9 | re.match("""\triple double""")
RUF039.py:7:13: RUF039 First argument to `re.finditer()` is not raw string
|
5 | re.compile('single free-spacing', flags=re.X)
@@ -43,7 +53,7 @@ RUF039.py:7:13: RUF039 First argument to `re.finditer()` is not raw string
|
= help: Replace with raw string
RUF039.py:8:14: RUF039 First argument to `re.fullmatch()` is not raw string
RUF039.py:8:14: RUF039 [*] First argument to `re.fullmatch()` is not raw string
|
6 | re.findall('si\ngle')
7 | re.finditer("dou\ble")
@@ -54,7 +64,17 @@ RUF039.py:8:14: RUF039 First argument to `re.fullmatch()` is not raw string
|
= help: Replace with raw string
RUF039.py:9:10: RUF039 First argument to `re.match()` is not raw string
Unsafe fix
5 5 | re.compile('single free-spacing', flags=re.X)
6 6 | re.findall('si\ngle')
7 7 | re.finditer("dou\ble")
8 |-re.fullmatch('''t\riple single''')
8 |+re.fullmatch(r'''t\riple single''')
9 9 | re.match("""\triple double""")
10 10 | re.search('two', 'args')
11 11 | re.split("raw", r'second')
RUF039.py:9:10: RUF039 [*] First argument to `re.match()` is not raw string
|
7 | re.finditer("dou\ble")
8 | re.fullmatch('''t\riple single''')
@@ -65,6 +85,16 @@ RUF039.py:9:10: RUF039 First argument to `re.match()` is not raw string
|
= help: Replace with raw string
Unsafe fix
6 6 | re.findall('si\ngle')
7 7 | re.finditer("dou\ble")
8 8 | re.fullmatch('''t\riple single''')
9 |-re.match("""\triple double""")
9 |+re.match(r"""\triple double""")
10 10 | re.search('two', 'args')
11 11 | re.split("raw", r'second')
12 12 | re.sub(u'''nicode''', u"f(?i)rst")
RUF039.py:10:11: RUF039 [*] First argument to `re.search()` is not raw string
|
8 | re.fullmatch('''t\riple single''')
@@ -117,7 +147,7 @@ RUF039.py:12:8: RUF039 First argument to `re.sub()` is not raw string
|
= help: Replace with raw string
RUF039.py:13:9: RUF039 First argument to `re.subn()` is not raw bytes literal
RUF039.py:13:9: RUF039 [*] First argument to `re.subn()` is not raw bytes literal
|
11 | re.split("raw", r'second')
12 | re.sub(u'''nicode''', u"f(?i)rst")
@@ -128,6 +158,16 @@ RUF039.py:13:9: RUF039 First argument to `re.subn()` is not raw bytes literal
|
= help: Replace with raw bytes literal
Safe fix
10 10 | re.search('two', 'args')
11 11 | re.split("raw", r'second')
12 12 | re.sub(u'''nicode''', u"f(?i)rst")
13 |-re.subn(b"""ytes are""", f"\u006e")
13 |+re.subn(rb"""ytes are""", f"\u006e")
14 14 |
15 15 | regex.compile('single free-spacing', flags=regex.X)
16 16 | regex.findall('si\ngle')
RUF039.py:15:15: RUF039 [*] First argument to `regex.compile()` is not raw string
|
13 | re.subn(b"""ytes are""", f"\u006e")
@@ -149,7 +189,7 @@ RUF039.py:15:15: RUF039 [*] First argument to `regex.compile()` is not raw strin
17 17 | regex.finditer("dou\ble")
18 18 | regex.fullmatch('''t\riple single''')
RUF039.py:16:15: RUF039 First argument to `regex.findall()` is not raw string
RUF039.py:16:15: RUF039 [*] First argument to `regex.findall()` is not raw string
|
15 | regex.compile('single free-spacing', flags=regex.X)
16 | regex.findall('si\ngle')
@@ -159,6 +199,16 @@ RUF039.py:16:15: RUF039 First argument to `regex.findall()` is not raw string
|
= help: Replace with raw string
Unsafe fix
13 13 | re.subn(b"""ytes are""", f"\u006e")
14 14 |
15 15 | regex.compile('single free-spacing', flags=regex.X)
16 |-regex.findall('si\ngle')
16 |+regex.findall(r'si\ngle')
17 17 | regex.finditer("dou\ble")
18 18 | regex.fullmatch('''t\riple single''')
19 19 | regex.match("""\triple double""")
RUF039.py:17:16: RUF039 First argument to `regex.finditer()` is not raw string
|
15 | regex.compile('single free-spacing', flags=regex.X)
@@ -170,7 +220,7 @@ RUF039.py:17:16: RUF039 First argument to `regex.finditer()` is not raw string
|
= help: Replace with raw string
RUF039.py:18:17: RUF039 First argument to `regex.fullmatch()` is not raw string
RUF039.py:18:17: RUF039 [*] First argument to `regex.fullmatch()` is not raw string
|
16 | regex.findall('si\ngle')
17 | regex.finditer("dou\ble")
@@ -181,7 +231,17 @@ RUF039.py:18:17: RUF039 First argument to `regex.fullmatch()` is not raw string
|
= help: Replace with raw string
RUF039.py:19:13: RUF039 First argument to `regex.match()` is not raw string
Unsafe fix
15 15 | regex.compile('single free-spacing', flags=regex.X)
16 16 | regex.findall('si\ngle')
17 17 | regex.finditer("dou\ble")
18 |-regex.fullmatch('''t\riple single''')
18 |+regex.fullmatch(r'''t\riple single''')
19 19 | regex.match("""\triple double""")
20 20 | regex.search('two', 'args')
21 21 | regex.split("raw", r'second')
RUF039.py:19:13: RUF039 [*] First argument to `regex.match()` is not raw string
|
17 | regex.finditer("dou\ble")
18 | regex.fullmatch('''t\riple single''')
@@ -192,6 +252,16 @@ RUF039.py:19:13: RUF039 First argument to `regex.match()` is not raw string
|
= help: Replace with raw string
Unsafe fix
16 16 | regex.findall('si\ngle')
17 17 | regex.finditer("dou\ble")
18 18 | regex.fullmatch('''t\riple single''')
19 |-regex.match("""\triple double""")
19 |+regex.match(r"""\triple double""")
20 20 | regex.search('two', 'args')
21 21 | regex.split("raw", r'second')
22 22 | regex.sub(u'''nicode''', u"f(?i)rst")
RUF039.py:20:14: RUF039 [*] First argument to `regex.search()` is not raw string
|
18 | regex.fullmatch('''t\riple single''')
@@ -244,7 +314,7 @@ RUF039.py:22:11: RUF039 First argument to `regex.sub()` is not raw string
|
= help: Replace with raw string
RUF039.py:23:12: RUF039 First argument to `regex.subn()` is not raw bytes literal
RUF039.py:23:12: RUF039 [*] First argument to `regex.subn()` is not raw bytes literal
|
21 | regex.split("raw", r'second')
22 | regex.sub(u'''nicode''', u"f(?i)rst")
@@ -255,6 +325,16 @@ RUF039.py:23:12: RUF039 First argument to `regex.subn()` is not raw bytes litera
|
= help: Replace with raw bytes literal
Safe fix
20 20 | regex.search('two', 'args')
21 21 | regex.split("raw", r'second')
22 22 | regex.sub(u'''nicode''', u"f(?i)rst")
23 |-regex.subn(b"""ytes are""", f"\u006e")
23 |+regex.subn(rb"""ytes are""", f"\u006e")
24 24 |
25 25 | regex.template("""(?m)
26 26 | (?:ulti)?
RUF039.py:25:16: RUF039 [*] First argument to `regex.template()` is not raw string
|
23 | regex.subn(b"""ytes are""", f"\u006e")
@@ -278,3 +358,111 @@ RUF039.py:25:16: RUF039 [*] First argument to `regex.template()` is not raw stri
26 26 | (?:ulti)?
27 27 | (?=(?<!(?<=(?!l)))
28 28 | l(?i:ne)
RUF039.py:59:12: RUF039 [*] First argument to `re.compile()` is not raw string
|
58 | # https://github.com/astral-sh/ruff/issues/16713
59 | re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF039
60 | re.compile("\b") # without fix
61 | re.compile("\"") # without fix
|
= help: Replace with raw string
Unsafe fix
56 56 |
57 57 |
58 58 | # https://github.com/astral-sh/ruff/issues/16713
59 |-re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
59 |+re.compile(r"\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
60 60 | re.compile("\b") # without fix
61 61 | re.compile("\"") # without fix
62 62 | re.compile("\'") # without fix
RUF039.py:60:12: RUF039 First argument to `re.compile()` is not raw string
|
58 | # https://github.com/astral-sh/ruff/issues/16713
59 | re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
60 | re.compile("\b") # without fix
| ^^^^ RUF039
61 | re.compile("\"") # without fix
62 | re.compile("\'") # without fix
|
= help: Replace with raw string
RUF039.py:61:12: RUF039 First argument to `re.compile()` is not raw string
|
59 | re.compile("\a\f\n\r\t\u27F2\U0001F0A1\v\x41") # with unsafe fix
60 | re.compile("\b") # without fix
61 | re.compile("\"") # without fix
| ^^^^ RUF039
62 | re.compile("\'") # without fix
63 | re.compile('\"') # without fix
|
= help: Replace with raw string
RUF039.py:62:12: RUF039 First argument to `re.compile()` is not raw string
|
60 | re.compile("\b") # without fix
61 | re.compile("\"") # without fix
62 | re.compile("\'") # without fix
| ^^^^ RUF039
63 | re.compile('\"') # without fix
64 | re.compile('\'') # without fix
|
= help: Replace with raw string
RUF039.py:63:12: RUF039 First argument to `re.compile()` is not raw string
|
61 | re.compile("\"") # without fix
62 | re.compile("\'") # without fix
63 | re.compile('\"') # without fix
| ^^^^ RUF039
64 | re.compile('\'') # without fix
65 | re.compile("\\") # without fix
|
= help: Replace with raw string
RUF039.py:64:12: RUF039 First argument to `re.compile()` is not raw string
|
62 | re.compile("\'") # without fix
63 | re.compile('\"') # without fix
64 | re.compile('\'') # without fix
| ^^^^ RUF039
65 | re.compile("\\") # without fix
66 | re.compile("\101") # without fix
|
= help: Replace with raw string
RUF039.py:65:12: RUF039 First argument to `re.compile()` is not raw string
|
63 | re.compile('\"') # without fix
64 | re.compile('\'') # without fix
65 | re.compile("\\") # without fix
| ^^^^ RUF039
66 | re.compile("\101") # without fix
67 | re.compile("a\
|
= help: Replace with raw string
RUF039.py:66:12: RUF039 First argument to `re.compile()` is not raw string
|
64 | re.compile('\'') # without fix
65 | re.compile("\\") # without fix
66 | re.compile("\101") # without fix
| ^^^^^^ RUF039
67 | re.compile("a\
68 | b") # without fix
|
= help: Replace with raw string
RUF039.py:67:12: RUF039 First argument to `re.compile()` is not raw string
|
65 | re.compile("\\") # without fix
66 | re.compile("\101") # without fix
67 | re.compile("a\
| ____________^
68 | | b") # without fix
| |__^ RUF039
|
= help: Replace with raw string

View File

@@ -65,7 +65,7 @@ RUF039_concat.py:12:5: RUF039 [*] First argument to `re.findall()` is not raw st
14 14 | """
15 15 | )
RUF039_concat.py:26:5: RUF039 First argument to `re.match()` is not raw bytes literal
RUF039_concat.py:26:5: RUF039 [*] First argument to `re.match()` is not raw bytes literal
|
24 | )
25 | re.match(
@@ -76,6 +76,16 @@ RUF039_concat.py:26:5: RUF039 First argument to `re.match()` is not raw bytes li
|
= help: Replace with raw bytes literal
Safe fix
23 23 | f'much?'
24 24 | )
25 25 | re.match(
26 |- b'reak'
26 |+ rb'reak'
27 27 | br'eak'
28 28 | )
29 29 | re.search(
RUF039_concat.py:30:8: RUF039 First argument to `re.search()` is not raw string
|
28 | )
@@ -295,7 +305,7 @@ RUF039_concat.py:52:5: RUF039 [*] First argument to `regex.findall()` is not raw
54 54 | """
55 55 | )
RUF039_concat.py:66:5: RUF039 First argument to `regex.match()` is not raw bytes literal
RUF039_concat.py:66:5: RUF039 [*] First argument to `regex.match()` is not raw bytes literal
|
64 | )
65 | regex.match(
@@ -306,6 +316,16 @@ RUF039_concat.py:66:5: RUF039 First argument to `regex.match()` is not raw bytes
|
= help: Replace with raw bytes literal
Safe fix
63 63 | f'much?'
64 64 | )
65 65 | regex.match(
66 |- b'reak'
66 |+ rb'reak'
67 67 | br'eak'
68 68 | )
69 69 | regex.search(
RUF039_concat.py:70:8: RUF039 First argument to `regex.search()` is not raw string
|
68 | )
@@ -460,3 +480,223 @@ RUF039_concat.py:78:24: RUF039 [*] First argument to `regex.subn()` is not raw s
79 79 |
80 80 |
81 81 | regex.template(
RUF039_concat.py:98:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
96 | # https://github.com/astral-sh/ruff/issues/16713
97 | re.compile(
98 | "["
| ^^^ RUF039
99 | "\U0001F600-\U0001F64F" # emoticons
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
|
= help: Replace with raw string
Safe fix
95 95 |
96 96 | # https://github.com/astral-sh/ruff/issues/16713
97 97 | re.compile(
98 |- "["
98 |+ r"["
99 99 | "\U0001F600-\U0001F64F" # emoticons
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
RUF039_concat.py:99:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
97 | re.compile(
98 | "["
99 | "\U0001F600-\U0001F64F" # emoticons
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
|
= help: Replace with raw string
Unsafe fix
96 96 | # https://github.com/astral-sh/ruff/issues/16713
97 97 | re.compile(
98 98 | "["
99 |- "\U0001F600-\U0001F64F" # emoticons
99 |+ r"\U0001F600-\U0001F64F" # emoticons
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
RUF039_concat.py:100:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
98 | "["
99 | "\U0001F600-\U0001F64F" # emoticons
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
|
= help: Replace with raw string
Unsafe fix
97 97 | re.compile(
98 98 | "["
99 99 | "\U0001F600-\U0001F64F" # emoticons
100 |- "\U0001F300-\U0001F5FF" # symbols & pictographs
100 |+ r"\U0001F300-\U0001F5FF" # symbols & pictographs
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 103 | "\U00002702-\U000027B0"
RUF039_concat.py:101:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
99 | "\U0001F600-\U0001F64F" # emoticons
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 | "\U00002702-\U000027B0"
|
= help: Replace with raw string
Unsafe fix
98 98 | "["
99 99 | "\U0001F600-\U0001F64F" # emoticons
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 |- "\U0001F680-\U0001F6FF" # transport & map symbols
101 |+ r"\U0001F680-\U0001F6FF" # transport & map symbols
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 103 | "\U00002702-\U000027B0"
104 104 | "\U000024C2-\U0001F251"
RUF039_concat.py:102:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
103 | "\U00002702-\U000027B0"
104 | "\U000024C2-\U0001F251"
|
= help: Replace with raw string
Unsafe fix
99 99 | "\U0001F600-\U0001F64F" # emoticons
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 |- "\U0001F1E0-\U0001F1FF" # flags (iOS)
102 |+ r"\U0001F1E0-\U0001F1FF" # flags (iOS)
103 103 | "\U00002702-\U000027B0"
104 104 | "\U000024C2-\U0001F251"
105 105 | "\u200d" # zero width joiner
RUF039_concat.py:103:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 | "\U00002702-\U000027B0"
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
104 | "\U000024C2-\U0001F251"
105 | "\u200d" # zero width joiner
|
= help: Replace with raw string
Unsafe fix
100 100 | "\U0001F300-\U0001F5FF" # symbols & pictographs
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 |- "\U00002702-\U000027B0"
103 |+ r"\U00002702-\U000027B0"
104 104 | "\U000024C2-\U0001F251"
105 105 | "\u200d" # zero width joiner
106 106 | "\u200c" # zero width non-joiner
RUF039_concat.py:104:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 | "\U00002702-\U000027B0"
104 | "\U000024C2-\U0001F251"
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF039
105 | "\u200d" # zero width joiner
106 | "\u200c" # zero width non-joiner
|
= help: Replace with raw string
Unsafe fix
101 101 | "\U0001F680-\U0001F6FF" # transport & map symbols
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 103 | "\U00002702-\U000027B0"
104 |- "\U000024C2-\U0001F251"
104 |+ r"\U000024C2-\U0001F251"
105 105 | "\u200d" # zero width joiner
106 106 | "\u200c" # zero width non-joiner
107 107 | "\\u200c" # must not be escaped in a raw string
RUF039_concat.py:105:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
103 | "\U00002702-\U000027B0"
104 | "\U000024C2-\U0001F251"
105 | "\u200d" # zero width joiner
| ^^^^^^^^ RUF039
106 | "\u200c" # zero width non-joiner
107 | "\\u200c" # must not be escaped in a raw string
|
= help: Replace with raw string
Unsafe fix
102 102 | "\U0001F1E0-\U0001F1FF" # flags (iOS)
103 103 | "\U00002702-\U000027B0"
104 104 | "\U000024C2-\U0001F251"
105 |- "\u200d" # zero width joiner
105 |+ r"\u200d" # zero width joiner
106 106 | "\u200c" # zero width non-joiner
107 107 | "\\u200c" # must not be escaped in a raw string
108 108 | "]+",
RUF039_concat.py:106:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
104 | "\U000024C2-\U0001F251"
105 | "\u200d" # zero width joiner
106 | "\u200c" # zero width non-joiner
| ^^^^^^^^ RUF039
107 | "\\u200c" # must not be escaped in a raw string
108 | "]+",
|
= help: Replace with raw string
Unsafe fix
103 103 | "\U00002702-\U000027B0"
104 104 | "\U000024C2-\U0001F251"
105 105 | "\u200d" # zero width joiner
106 |- "\u200c" # zero width non-joiner
106 |+ r"\u200c" # zero width non-joiner
107 107 | "\\u200c" # must not be escaped in a raw string
108 108 | "]+",
109 109 | flags=re.UNICODE,
RUF039_concat.py:107:5: RUF039 First argument to `re.compile()` is not raw string
|
105 | "\u200d" # zero width joiner
106 | "\u200c" # zero width non-joiner
107 | "\\u200c" # must not be escaped in a raw string
| ^^^^^^^^^ RUF039
108 | "]+",
109 | flags=re.UNICODE,
|
= help: Replace with raw string
RUF039_concat.py:108:5: RUF039 [*] First argument to `re.compile()` is not raw string
|
106 | "\u200c" # zero width non-joiner
107 | "\\u200c" # must not be escaped in a raw string
108 | "]+",
| ^^^^ RUF039
109 | flags=re.UNICODE,
110 | )
|
= help: Replace with raw string
Safe fix
105 105 | "\u200d" # zero width joiner
106 106 | "\u200c" # zero width non-joiner
107 107 | "\\u200c" # must not be escaped in a raw string
108 |- "]+",
108 |+ r"]+",
109 109 | flags=re.UNICODE,
110 110 | )

View File

@@ -0,0 +1,11 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF039_py_version_sensitive.py:3:12: RUF039 First argument to `re.compile()` is not raw string
|
1 | import re
2 |
3 | re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
= help: Replace with raw string

View File

@@ -0,0 +1,17 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF039_py_version_sensitive.py:3:12: RUF039 [*] First argument to `re.compile()` is not raw string
|
1 | import re
2 |
3 | re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF039
|
= help: Replace with raw string
Unsafe fix
1 1 | import re
2 2 |
3 |-re.compile("\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix
3 |+re.compile(r"\N{Partial Differential}") # with unsafe fix if python target is 3.8 or higher, else without fix

View File

@@ -708,23 +708,10 @@ pub struct ComparableTString<'a> {
}
impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
// The approach taken below necessarily deviates from the
// corresponding implementation for [`ast::FStringValue`].
// The reason is that a t-string value is composed of _three_
// non-comparable parts: literals, f-string expressions, and
// t-string interpolations. Since we have merged the AST nodes
// that capture f-string expressions and t-string interpolations
// into the shared [`ast::InterpolatedElement`], we must
// be careful to distinguish between them here.
// We model a [`ComparableTString`] on the actual
// [CPython implementation] of a `string.templatelib.Template` object.
//
// Consequently, we model a [`ComparableTString`] on the actual
// [CPython implementation] of a `string.templatelib.Template` object:
// it is composed of `strings` and `interpolations`. In CPython,
// the `strings` field is a tuple of honest strings (since f-strings
// are evaluated). Our `strings` field will house both f-string
// expressions and string literals.
//
// Finally, as in CPython, we must be careful to ensure that the length
// As in CPython, we must be careful to ensure that the length
// of `strings` is always one more than the length of `interpolations` -
// that way we can recover the original reading order by interleaving
// starting with `strings`. This is how we can tell the
@@ -768,19 +755,6 @@ impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
.push(ComparableInterpolatedStringElement::Literal("".into()));
}
fn push_fstring_expression(&mut self, expression: &'a ast::InterpolatedElement) {
if let Some(ComparableInterpolatedStringElement::Literal(last_literal)) =
self.strings.last()
{
// Recall that we insert empty strings after
// each interpolation. If we encounter an f-string
// expression, we replace the empty string with it.
if last_literal.is_empty() {
self.strings.pop();
}
}
self.strings.push(expression.into());
}
fn push_tstring_interpolation(&mut self, expression: &'a ast::InterpolatedElement) {
self.interpolations.push(expression.into());
self.start_new_literal();
@@ -789,34 +763,13 @@ impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
let mut collector = Collector::default();
for part in value {
match part {
ast::TStringPart::Literal(string_literal) => {
collector.push_literal(&string_literal.value);
for element in value.elements() {
match element {
ast::InterpolatedStringElement::Literal(literal) => {
collector.push_literal(&literal.value);
}
ast::TStringPart::TString(fstring) => {
for element in &fstring.elements {
match element {
ast::InterpolatedStringElement::Literal(literal) => {
collector.push_literal(&literal.value);
}
ast::InterpolatedStringElement::Interpolation(interpolation) => {
collector.push_tstring_interpolation(interpolation);
}
}
}
}
ast::TStringPart::FString(fstring) => {
for element in &fstring.elements {
match element {
ast::InterpolatedStringElement::Literal(literal) => {
collector.push_literal(&literal.value);
}
ast::InterpolatedStringElement::Interpolation(expression) => {
collector.push_fstring_expression(expression);
}
}
}
ast::InterpolatedStringElement::Interpolation(interpolation) => {
collector.push_tstring_interpolation(interpolation);
}
}
}

View File

@@ -320,7 +320,7 @@ pub enum StringLikePartIter<'a> {
String(std::slice::Iter<'a, ast::StringLiteral>),
Bytes(std::slice::Iter<'a, ast::BytesLiteral>),
FString(std::slice::Iter<'a, ast::FStringPart>),
TString(std::slice::Iter<'a, ast::TStringPart>),
TString(std::slice::Iter<'a, ast::TString>),
}
impl<'a> Iterator for StringLikePartIter<'a> {
@@ -339,16 +339,7 @@ impl<'a> Iterator for StringLikePartIter<'a> {
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => {
let part = inner.next()?;
match part {
ast::TStringPart::Literal(string_literal) => {
StringLikePart::String(string_literal)
}
ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string),
ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => StringLikePart::TString(inner.next()?),
};
Some(part)
@@ -378,16 +369,7 @@ impl DoubleEndedIterator for StringLikePartIter<'_> {
ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => {
let part = inner.next_back()?;
match part {
ast::TStringPart::Literal(string_literal) => {
StringLikePart::String(string_literal)
}
ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string),
ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string),
}
}
StringLikePartIter::TString(inner) => StringLikePart::TString(inner.next_back()?),
};
Some(part)

View File

@@ -1274,6 +1274,7 @@ impl Truthiness {
Self::Unknown
}
}
Expr::TString(_) => Self::Truthy,
Expr::List(ast::ExprList { elts, .. })
| Expr::Set(ast::ExprSet { elts, .. })
| Expr::Tuple(ast::ExprTuple { elts, .. }) => {
@@ -1362,6 +1363,7 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
Expr::EllipsisLiteral(_) => true,
Expr::List(_) => true,
Expr::Tuple(_) => true,
Expr::TString(_) => true,
// These expressions must resolve to the inner expression.
Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
@@ -1386,7 +1388,6 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
// These literals may or may not be empty.
Expr::FString(f_string) => is_non_empty_f_string(f_string),
// These literals may or may not be empty.
Expr::TString(f_string) => is_non_empty_t_string(f_string),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(),
}
@@ -1403,76 +1404,6 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
})
}
/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an
/// f-string expression, or `false` if the expression may resolve to an empty string.
fn is_non_empty_t_string(expr: &ast::ExprTString) -> bool {
fn inner(expr: &Expr) -> bool {
match expr {
// When stringified, these expressions are always non-empty.
Expr::Lambda(_) => true,
Expr::Dict(_) => true,
Expr::Set(_) => true,
Expr::ListComp(_) => true,
Expr::SetComp(_) => true,
Expr::DictComp(_) => true,
Expr::Compare(_) => true,
Expr::NumberLiteral(_) => true,
Expr::BooleanLiteral(_) => true,
Expr::NoneLiteral(_) => true,
Expr::EllipsisLiteral(_) => true,
Expr::List(_) => true,
Expr::Tuple(_) => true,
// These expressions must resolve to the inner expression.
Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
Expr::Named(ast::ExprNamed { value, .. }) => inner(value),
// These expressions are complex. We can't determine whether they're empty or not.
Expr::BoolOp(ast::ExprBoolOp { .. }) => false,
Expr::BinOp(ast::ExprBinOp { .. }) => false,
Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false,
Expr::Generator(_) => false,
Expr::Await(_) => false,
Expr::Yield(_) => false,
Expr::YieldFrom(_) => false,
Expr::Call(_) => false,
Expr::Attribute(_) => false,
Expr::Subscript(_) => false,
Expr::Starred(_) => false,
Expr::Name(_) => false,
Expr::Slice(_) => false,
Expr::IpyEscapeCommand(_) => false,
// These literals may or may not be empty.
Expr::FString(f_string) => is_non_empty_f_string(f_string),
// These literals may or may not be empty.
Expr::TString(t_string) => is_non_empty_t_string(t_string),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(),
}
}
expr.value.iter().any(|part| match part {
ast::TStringPart::Literal(string_literal) => !string_literal.is_empty(),
ast::TStringPart::TString(t_string) => {
t_string.elements.iter().all(|element| match element {
ast::InterpolatedStringElement::Literal(string_literal) => {
!string_literal.is_empty()
}
ast::InterpolatedStringElement::Interpolation(t_string) => {
inner(&t_string.expression)
}
})
}
ast::TStringPart::FString(f_string) => {
f_string.elements.iter().all(|element| match element {
InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(),
InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression),
})
}
})
}
/// Returns `true` if the expression definitely resolves to the empty string, when used as an f-string
/// expression.
fn is_empty_f_string(expr: &ast::ExprFString) -> bool {

View File

@@ -29,6 +29,10 @@ impl Name {
Self(compact_str::CompactString::const_new(name))
}
pub fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit();
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}

View File

@@ -171,18 +171,8 @@ impl ast::ExprTString {
node_index: _,
} = self;
for t_string_part in value {
match t_string_part {
ast::TStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
ast::TStringPart::FString(f_string) => {
visitor.visit_f_string(f_string);
}
ast::TStringPart::TString(t_string) => {
visitor.visit_t_string(t_string);
}
}
for t_string in value {
visitor.visit_t_string(t_string);
}
}
}

View File

@@ -597,8 +597,8 @@ impl ExprTString {
/// otherwise.
pub const fn as_single_part_tstring(&self) -> Option<&TString> {
match &self.value.inner {
TStringValueInner::Single(TStringPart::TString(tstring)) => Some(tstring),
_ => None,
TStringValueInner::Single(tstring) => Some(tstring),
TStringValueInner::Concatenated(_) => None,
}
}
}
@@ -614,7 +614,7 @@ impl TStringValue {
/// Creates a new t-string literal with a single [`TString`] part.
pub fn single(value: TString) -> Self {
Self {
inner: TStringValueInner::Single(TStringPart::TString(value)),
inner: TStringValueInner::Single(value),
}
}
@@ -625,7 +625,7 @@ impl TStringValue {
///
/// Panics if `values` has less than 2 elements.
/// Use [`TStringValue::single`] instead.
pub fn concatenated(values: Vec<TStringPart>) -> Self {
pub fn concatenated(values: Vec<TString>) -> Self {
assert!(
values.len() > 1,
"Use `TStringValue::single` to create single-part t-strings"
@@ -640,78 +640,52 @@ impl TStringValue {
matches!(self.inner, TStringValueInner::Concatenated(_))
}
/// Returns a slice of all the [`TStringPart`]s contained in this value.
pub fn as_slice(&self) -> &[TStringPart] {
/// Returns a slice of all the [`TString`]s contained in this value.
pub fn as_slice(&self) -> &[TString] {
match &self.inner {
TStringValueInner::Single(part) => std::slice::from_ref(part),
TStringValueInner::Concatenated(parts) => parts,
}
}
/// Returns a mutable slice of all the [`TStringPart`]s contained in this value.
fn as_mut_slice(&mut self) -> &mut [TStringPart] {
/// Returns a mutable slice of all the [`TString`]s contained in this value.
fn as_mut_slice(&mut self) -> &mut [TString] {
match &mut self.inner {
TStringValueInner::Single(part) => std::slice::from_mut(part),
TStringValueInner::Concatenated(parts) => parts,
}
}
/// Returns an iterator over all the [`TStringPart`]s contained in this value.
pub fn iter(&self) -> Iter<TStringPart> {
/// Returns an iterator over all the [`TString`]s contained in this value.
pub fn iter(&self) -> Iter<TString> {
self.as_slice().iter()
}
/// Returns an iterator over all the [`TStringPart`]s contained in this value
/// Returns an iterator over all the [`TString`]s contained in this value
/// that allows modification.
pub fn iter_mut(&mut self) -> IterMut<TStringPart> {
pub fn iter_mut(&mut self) -> IterMut<TString> {
self.as_mut_slice().iter_mut()
}
/// Returns an iterator over the [`StringLiteral`] parts contained in this value.
///
/// Note that this doesn't recurse into the t-string parts. For example,
///
/// ```python
/// "foo" t"bar {x}" "baz" t"qux"
/// ```
///
/// Here, the string literal parts returned would be `"foo"` and `"baz"`.
pub fn literals(&self) -> impl Iterator<Item = &StringLiteral> {
self.iter().filter_map(|part| part.as_literal())
}
/// Returns an iterator over the [`TString`] parts contained in this value.
///
/// Note that this doesn't recurse into the t-string parts. For example,
///
/// ```python
/// "foo" t"bar {x}" "baz" t"qux"
/// ```
///
/// Here, the t-string parts returned would be `f"bar {x}"` and `f"qux"`.
pub fn t_strings(&self) -> impl Iterator<Item = &TString> {
self.iter().filter_map(|part| part.as_t_string())
}
/// Returns an iterator over all the [`InterpolatedStringElement`] contained in this value.
///
/// An t-string element is what makes up an [`TString`] i.e., it is either a
/// An interpolated string element is what makes up an [`TString`] i.e., it is either a
/// string literal or an interpolation. In the following example,
///
/// ```python
/// "foo" t"bar {x}" "baz" t"qux"
/// t"foo" t"bar {x}" t"baz" t"qux"
/// ```
///
/// The t-string elements returned would be string literal (`"bar "`),
/// The interpolated string elements returned would be string literal (`"bar "`),
/// interpolation (`x`) and string literal (`"qux"`).
pub fn elements(&self) -> impl Iterator<Item = &InterpolatedStringElement> {
self.t_strings().flat_map(|fstring| fstring.elements.iter())
self.iter().flat_map(|tstring| tstring.elements.iter())
}
}
impl<'a> IntoIterator for &'a TStringValue {
type Item = &'a TStringPart;
type IntoIter = Iter<'a, TStringPart>;
type Item = &'a TString;
type IntoIter = Iter<'a, TString>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
@@ -719,8 +693,8 @@ impl<'a> IntoIterator for &'a TStringValue {
}
impl<'a> IntoIterator for &'a mut TStringValue {
type Item = &'a mut TStringPart;
type IntoIter = IterMut<'a, TStringPart>;
type Item = &'a mut TString;
type IntoIter = IterMut<'a, TString>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
@@ -731,43 +705,10 @@ impl<'a> IntoIterator for &'a mut TStringValue {
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
enum TStringValueInner {
/// A single t-string i.e., `t"foo"`.
///
/// This is always going to be `TStringPart::TString` variant which is
/// maintained by the `TStringValue::single` constructor.
Single(TStringPart),
Single(TString),
/// An implicitly concatenated t-string i.e., `"foo" t"bar {x}"`.
Concatenated(Vec<TStringPart>),
}
/// An t-string part which is either a string literal, an f-string,
/// or a t-string.
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
pub enum TStringPart {
Literal(StringLiteral),
FString(FString),
TString(TString),
}
impl TStringPart {
pub fn quote_style(&self) -> Quote {
match self {
Self::Literal(string_literal) => string_literal.flags.quote_style(),
Self::FString(f_string) => f_string.flags.quote_style(),
Self::TString(t_string) => t_string.flags.quote_style(),
}
}
}
impl Ranged for TStringPart {
fn range(&self) -> TextRange {
match self {
TStringPart::Literal(string_literal) => string_literal.range(),
TStringPart::FString(f_string) => f_string.range(),
TStringPart::TString(t_string) => t_string.range(),
}
}
/// An implicitly concatenated t-string i.e., `t"foo" t"bar {x}"`.
Concatenated(Vec<TString>),
}
pub trait StringFlags: Copy {
@@ -1237,6 +1178,12 @@ pub struct TString {
pub flags: TStringFlags,
}
impl TString {
pub fn quote_style(&self) -> Quote {
self.flags.quote_style()
}
}
impl From<TString> for Expr {
fn from(payload: TString) -> Self {
ExprTString {

View File

@@ -7,8 +7,8 @@ use crate::{
self as ast, Alias, AnyParameterRef, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension,
Decorator, ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringPart,
InterpolatedStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern,
PatternArguments, PatternKeyword, Stmt, StringLiteral, TString, TStringPart, TypeParam,
TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
PatternArguments, PatternKeyword, Stmt, StringLiteral, TString, TypeParam, TypeParamParamSpec,
TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
};
/// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order.
@@ -547,14 +547,8 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
}
}
Expr::TString(ast::ExprTString { value, .. }) => {
for part in value {
match part {
TStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
TStringPart::FString(f_string) => visitor.visit_f_string(f_string),
TStringPart::TString(t_string) => visitor.visit_t_string(t_string),
}
for t_string in value {
visitor.visit_t_string(t_string);
}
}
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {

View File

@@ -533,18 +533,8 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
}
}
Expr::TString(ast::ExprTString { value, .. }) => {
for t_string_part in value.iter_mut() {
match t_string_part {
ast::TStringPart::Literal(string_literal) => {
visitor.visit_string_literal(string_literal);
}
ast::TStringPart::FString(f_string) => {
visitor.visit_f_string(f_string);
}
ast::TStringPart::TString(t_string) => {
visitor.visit_t_string(t_string);
}
}
for t_string in value.iter_mut() {
visitor.visit_t_string(t_string);
}
}
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {

View File

@@ -51,28 +51,12 @@ fn concatenated_fstrings_compare_equal() -> Result<(), ParseError> {
#[test]
fn concatenated_tstrings_compare_equal() -> Result<(), ParseError> {
let split_contents = r#"t"{foo!r} this" r"\n raw" t" and {bar!s} that""#;
let split_contents = r#"t"{foo!r} this" rt"\n raw" t" and {bar!s} that""#;
let value_contents = r#"t"{foo!r} this\\n raw and {bar!s} that""#;
assert_comparable(split_contents, value_contents)
}
#[test]
fn concatenated_f_and_t_strings_interwoven_compare_equal() -> Result<(), ParseError> {
let split_contents = r#"f"{foo} this " t"{bar}" "baz""#;
let value_contents = r#"f"{foo}" t" this {bar}" "baz""#;
assert_comparable(split_contents, value_contents)
}
#[test]
fn concatenated_f_and_t_strings_compare_unequal_when_swapped() -> Result<(), ParseError> {
let f_then_t_contents = r#"f"{foo!r} this" r"\n raw" t" and {bar!s} that""#;
let t_then_f_contents = r#"t"{foo!r} this" r"\n raw" f" and {bar!s} that""#;
assert_noncomparable(f_then_t_contents, t_then_f_contents)
}
#[test]
fn t_strings_literal_order_matters_compare_unequal() -> Result<(), ParseError> {
let interp_then_literal_contents = r#"t"{foo}bar""#;
@@ -80,11 +64,3 @@ fn t_strings_literal_order_matters_compare_unequal() -> Result<(), ParseError> {
assert_noncomparable(interp_then_literal_contents, literal_then_interp_contents)
}
#[test]
fn t_strings_empty_concat_equal() -> Result<(), ParseError> {
let empty_literal = r#""" t"hey{foo}""#;
let empty_f_string = r#"f""t"hey{foo}""#;
assert_comparable(empty_literal, empty_f_string)
}

View File

@@ -5,7 +5,8 @@ expression: trace
- ModModule
- StmtExpr
- ExprTString
- StringLiteral
- TString
- InterpolatedStringLiteralElement
- TString
- InterpolatedStringLiteralElement
- InterpolatedElement

View File

@@ -4,7 +4,8 @@ expression: trace
---
- StmtExpr
- ExprTString
- StringLiteral
- TString
- InterpolatedStringLiteralElement
- TString
- InterpolatedStringLiteralElement
- InterpolatedElement

View File

@@ -148,7 +148,7 @@ fn f_strings() {
#[test]
fn t_strings() {
let source = r"'pre' t'foo {bar:.{x}f} baz'";
let source = r"t'pre' t'foo {bar:.{x}f} baz'";
let trace = trace_source_order_visitation(source);

View File

@@ -157,7 +157,7 @@ fn f_strings() {
#[test]
fn t_strings() {
let source = r"'pre' t'foo {bar:.{x}f} baz'";
let source = r"t'pre' t'foo {bar:.{x}f} baz'";
let trace = trace_visitation(source);

View File

@@ -1538,19 +1538,9 @@ impl<'a> Generator<'a> {
fn unparse_t_string_value(&mut self, value: &ast::TStringValue) {
let mut first = true;
for t_string_part in value {
for t_string in value {
self.p_delim(&mut first, " ");
match t_string_part {
ast::TStringPart::Literal(string_literal) => {
self.unparse_string_literal(string_literal);
}
ast::TStringPart::FString(f_string) => {
self.unparse_interpolated_string(&f_string.elements, f_string.flags.into());
}
ast::TStringPart::TString(t_string) => {
self.unparse_interpolated_string(&t_string.elements, t_string.flags.into());
}
}
self.unparse_interpolated_string(&t_string.elements, t_string.flags.into());
}
}

View File

@@ -104,16 +104,13 @@ f"{10 + len('bar')=}" f'{10 + len("bar")=}'
# T-strings
##############################################################################
# Escape `{` and `}` when merging a t-string with a string
"a {not_a_variable}" t"b {10}" "c"
# Join, and break expressions
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{
expression
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
# Join, but don't break the expressions
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
t"test{
expression
@@ -171,22 +168,11 @@ t"test" tR"test"
"single" f""""single"""
"single" t""""single"""
t"single" t""""single"""
b"single" b"""triple"""
##############################################################################
# Don't join t-strings and f-strings
##############################################################################
t"{interp}" f"{expr}"
f"{expr}" t"{interp}"
f"{expr}" "string" t"{interp}"
##############################################################################
# Join strings in with statements
##############################################################################

View File

@@ -345,7 +345,7 @@ a[
b
] = (
t"ccccc{
expression}ccccccccccc" "cccccccccccccccccccccccc" # comment
expression}ccccccccccc" t"cccccccccccccccccccccccc" # comment
)
# Same but starting with a joined string. They should both result in the same formatting.
@@ -361,7 +361,7 @@ a[
aaaaaaa,
b
] = t"ccccc{
expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" t"ccccccccccccccccccccccccccccccccccccccccccc" # comment
# Split an overlong target, but join the string if it fits
@@ -370,7 +370,7 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" t"cccccccccccccccccccccccccccccc" # comment
)
# Split both if necessary and keep multiline
@@ -379,66 +379,66 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}cccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment
expression}cccccccccccccccccccccccccccccccc" t"ccccccccccccccccccccccccccccccc" # comment
)
# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma
aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
)
aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
)
# Don't inline t-strings that contain commented expressions
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
# Don't inline t-strings with multiline debug expressions:
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
b=}" "moreeeeeeeeeeeeeeeeeetest" # comment
b=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
@@ -499,7 +499,7 @@ a = (
)
logger.error(
f"Failed to run task {task} for job"
f"Failed to run task {task} for job"
f"with id {str(job.id)}" # type: ignore[union-attr]
)

View File

@@ -8,21 +8,21 @@ rt"Not-so-tricky \"quote"
# Regression test for tstrings dropping comments
result_f = (
'Traceback (most recent call last):\n'
t'Traceback (most recent call last):\n'
t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
' f()\n'
t' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
t' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
t' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
t' f()\n'
# XXX: The following line changes depending on whether the tests
# are run through the interactive interpreter or with -m
# It also varies depending on the platform (stack size)
# Fortunately, we don't care about exactness here, so we use regex
r' \[Previous line repeated (\d+) more times\]' '\n'
'RecursionError: maximum recursion depth exceeded\n'
rt' \[Previous line repeated (\d+) more times\]' t'\n'
t'RecursionError: maximum recursion depth exceeded\n'
)
@@ -31,7 +31,7 @@ result_f = (
(
t'{1}'
# comment 1
''
t''
)
(
@@ -655,7 +655,7 @@ hello {
# Implicit concatenated t-string containing quotes
_ = (
'This string should change its quotes to double quotes'
t'This string should change its quotes to double quotes'
t'This string uses double quotes in an expression {"it's a quote"}'
t'This t-string does not use any quotes.'
)

View File

@@ -110,16 +110,13 @@ f"{10 + len('bar')=}" f'{10 + len("bar")=}'
# T-strings
##############################################################################
# Escape `{` and `}` when merging a t-string with a string
"a {not_a_variable}" t"b {10}" "c"
# Join, and break expressions
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{
expression
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
# Join, but don't break the expressions
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more"
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" t"more"
t"test{
expression
@@ -177,22 +174,11 @@ t"test" tR"test"
"single" f""""single"""
"single" t""""single"""
t"single" t""""single"""
b"single" b"""triple"""
##############################################################################
# Don't join t-strings and f-strings
##############################################################################
t"{interp}" f"{expr}"
f"{expr}" t"{interp}"
f"{expr}" "string" t"{interp}"
##############################################################################
# Join strings in with statements
##############################################################################
@@ -521,9 +507,6 @@ f"{10 + len('bar')=}" f'{10 + len("bar")=}'
# T-strings
##############################################################################
# Escape `{` and `}` when merging a t-string with a string
t"a {{not_a_variable}}b {10}c"
# Join, and break expressions
t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{
expression
@@ -583,22 +566,11 @@ t"test" Rt"test"
"single" f""""single"""
"single" t""""single"""
t"single" t""""single"""
b"single" b"""triple"""
##############################################################################
# Don't join t-strings and f-strings
##############################################################################
t"{interp}" f"{expr}"
f"{expr}" t"{interp}"
f"{expr}" "string" t"{interp}"
##############################################################################
# Join strings in with statements
##############################################################################
@@ -905,7 +877,7 @@ f"aaaaaaaaaaaaaaaa \
```diff
--- Stable
+++ Preview
@@ -302,9 +302,12 @@
@@ -288,9 +288,12 @@
##############################################################################
# Use can_omit_optional_parentheses layout to avoid an instability where the formatter
# picks the can_omit_optional_parentheses layout when the strings are joined.

View File

@@ -351,7 +351,7 @@ a[
b
] = (
t"ccccc{
expression}ccccccccccc" "cccccccccccccccccccccccc" # comment
expression}ccccccccccc" t"cccccccccccccccccccccccc" # comment
)
# Same but starting with a joined string. They should both result in the same formatting.
@@ -367,7 +367,7 @@ a[
aaaaaaa,
b
] = t"ccccc{
expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" t"ccccccccccccccccccccccccccccccccccccccccccc" # comment
# Split an overlong target, but join the string if it fits
@@ -376,7 +376,7 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment
expression}ccccccccccc" t"cccccccccccccccccccccccccccccc" # comment
)
# Split both if necessary and keep multiline
@@ -385,66 +385,66 @@ a[
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{
expression}cccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment
expression}cccccccccccccccccccccccccccccccc" t"ccccccccccccccccccccccccccccccc" # comment
)
# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma
aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
)
aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
}" t"moreeeeeeeeeeeeeeeeeeee" t"test" # comment
)
# Don't inline t-strings that contain commented expressions
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
]}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
# Don't inline t-strings with multiline debug expressions:
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
b=}" "moreeeeeeeeeeeeeeeeeetest" # comment
b=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
a=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
=}" t"moreeeeeeeeeeeeeeeeeetest" # comment
)
@@ -505,7 +505,7 @@ a = (
)
logger.error(
f"Failed to run task {task} for job"
f"Failed to run task {task} for job"
f"with id {str(job.id)}" # type: ignore[union-attr]
)
@@ -909,7 +909,7 @@ a[aaaaaaa, b] = t"ccccc{expression}ccccccccccccccccccccccccccccccccccc" # comme
# The string gets parenthesized because it, with the inlined comment, exceeds the line length limit.
a[aaaaaaa, b] = (
t"ccccc{expression}ccccccccccc"
"ccccccccccccccccccccccccccccccccccccccccccc"
t"ccccccccccccccccccccccccccccccccccccccccccc"
) # comment
@@ -925,7 +925,7 @@ a[
aaaaaaa, b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
t"ccccc{expression}cccccccccccccccccccccccccccccccc"
"ccccccccccccccccccccccccccccccc"
t"ccccccccccccccccccccccccccccccc"
) # comment
# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma
@@ -935,8 +935,8 @@ aaaaaaaaaaaaaaaaaa = (
a,
]
}"
"moreeeeeeeeeeeeeeeeeeee"
"test"
t"moreeeeeeeeeeeeeeeeeeee"
t"test"
) # comment
aaaaaaaaaaaaaaaaaa = (
@@ -945,8 +945,8 @@ aaaaaaaaaaaaaaaaaa = (
a,
]
}"
"moreeeeeeeeeeeeeeeeeeee"
"test" # comment
t"moreeeeeeeeeeeeeeeeeeee"
t"test" # comment
)
aaaaa[aaaaaaaaaaa] = (
@@ -955,8 +955,8 @@ aaaaa[aaaaaaaaaaa] = (
a,
]
}"
"moreeeeeeeeeeeeeeeeeeee"
"test"
t"moreeeeeeeeeeeeeeeeeeee"
t"test"
) # comment
aaaaa[aaaaaaaaaaa] = (
@@ -965,8 +965,8 @@ aaaaa[aaaaaaaaaaa] = (
a,
]
}"
"moreeeeeeeeeeeeeeeeeeee"
"test" # comment
t"moreeeeeeeeeeeeeeeeeeee"
t"test" # comment
)
# Don't inline t-strings that contain commented expressions
@@ -976,7 +976,7 @@ aaaaaaaaaaaaaaaaaa = (
a # comment
]
}"
"moreeeeeeeeeeeeeeeeeetest" # comment
t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
@@ -985,38 +985,38 @@ aaaaa[aaaaaaaaaaa] = (
a # comment
]
}"
"moreeeeeeeeeeeeeeeeeetest" # comment
t"moreeeeeeeeeeeeeeeeeetest" # comment
)
# Don't inline t-strings with multiline debug expressions:
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}"
"moreeeeeeeeeeeeeeeeeetest" # comment
t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
b=}"
"moreeeeeeeeeeeeeeeeeetest" # comment
t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}"
"moreeeeeeeeeeeeeeeeeetest" # comment
t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}"
"moreeeeeeeeeeeeeeeeeetest" # comment
t"moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
t"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}"
"moreeeeeeeeeeeeeeeeeetest" # comment
t"moreeeeeeeeeeeeeeeeeetest" # comment
)

View File

@@ -14,21 +14,21 @@ rt"Not-so-tricky \"quote"
# Regression test for tstrings dropping comments
result_f = (
'Traceback (most recent call last):\n'
t'Traceback (most recent call last):\n'
t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
' f()\n'
t' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
t' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
t' f()\n'
t' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
t' f()\n'
# XXX: The following line changes depending on whether the tests
# are run through the interactive interpreter or with -m
# It also varies depending on the platform (stack size)
# Fortunately, we don't care about exactness here, so we use regex
r' \[Previous line repeated (\d+) more times\]' '\n'
'RecursionError: maximum recursion depth exceeded\n'
rt' \[Previous line repeated (\d+) more times\]' t'\n'
t'RecursionError: maximum recursion depth exceeded\n'
)
@@ -37,7 +37,7 @@ result_f = (
(
t'{1}'
# comment 1
''
t''
)
(
@@ -661,7 +661,7 @@ hello {
# Implicit concatenated t-string containing quotes
_ = (
'This string should change its quotes to double quotes'
t'This string should change its quotes to double quotes'
t'This string uses double quotes in an expression {"it's a quote"}'
t'This t-string does not use any quotes.'
)
@@ -761,22 +761,22 @@ rt"Not-so-tricky \"quote"
# Regression test for tstrings dropping comments
result_f = (
"Traceback (most recent call last):\n"
t"Traceback (most recent call last):\n"
t' File "{__file__}", line {lineno_f + 5}, in _check_recursive_traceback_display\n'
" f()\n"
t" f()\n"
t' File "{__file__}", line {lineno_f + 1}, in f\n'
" f()\n"
t" f()\n"
t' File "{__file__}", line {lineno_f + 1}, in f\n'
" f()\n"
t" f()\n"
t' File "{__file__}", line {lineno_f + 1}, in f\n'
" f()\n"
t" f()\n"
# XXX: The following line changes depending on whether the tests
# are run through the interactive interpreter or with -m
# It also varies depending on the platform (stack size)
# Fortunately, we don't care about exactness here, so we use regex
r" \[Previous line repeated (\d+) more times\]"
"\n"
"RecursionError: maximum recursion depth exceeded\n"
rt" \[Previous line repeated (\d+) more times\]"
t"\n"
t"RecursionError: maximum recursion depth exceeded\n"
)
@@ -785,7 +785,7 @@ result_f = (
(
t"{1}"
# comment 1
""
t""
)
(
@@ -1463,7 +1463,7 @@ hello {
# Implicit concatenated t-string containing quotes
_ = (
"This string should change its quotes to double quotes"
t"This string should change its quotes to double quotes"
t"This string uses double quotes in an expression {"it's a quote"}"
t"This t-string does not use any quotes."
)

View File

@@ -3,4 +3,3 @@ t"{hey}"
t'{there}'
t"""what's
happening?"""
"implicitly"t"concatenated"

View File

@@ -3,4 +3,3 @@ t"{hey}"
t'{there}'
t"""what's
happening?"""
"implicitly"t"concatenated"

View File

@@ -17,7 +17,7 @@ t"{ foo = !s }"
t"{ 1, 2 = }"
t'{t"{3.1415=:.1f}":*^20}'
{"foo " t"bar {x + y} " "baz": 10}
{t"foo " t"bar {x + y} " t"baz": 10}
match foo:
case "one":
pass
@@ -44,31 +44,18 @@ t"{x=!a}"
t"{x:.3f!r =}"
t"{x = !r :.3f}"
t"{x:.3f=!r}"
"hello" t"{x}"
t"hello" t"{x}"
t"{x}" t"{y}"
t"{x}" "world"
t"{x}" t"world"
t"Invalid args in command: {command, *args}"
"foo" t"{x}" "bar"
t"foo" t"{x}" t"bar"
(
t"a"
t"b"
"c"
t"c"
rt"d"
fr"e"
tr"e"
)
# With unicode strings
u"foo" t"{bar}" "baz" " some"
"foo" t"{bar}" u"baz" " some"
"foo" t"{bar}" "baz" u" some"
u"foo" t"bar {baz} really" u"bar" "no"
# With f-strings
f"{this}" t"{that}"
t"{this}"f"{that}"
t"{this}" "that" f"{other}"
f"one {this} two" "that" t"three {other} four"
# Nesting
t"{f"{t"{this}"}"}"

View File

@@ -1,4 +1,3 @@
use std::cmp::Ordering;
use std::ops::Deref;
use bitflags::bitflags;
@@ -1256,7 +1255,6 @@ impl<'src> Parser<'src> {
// t'{there}'
// t"""what's
// happening?"""
// "implicitly"t"concatenated"
// test_err template_strings_py313
// # parse_options: {"target-version": "3.13"}
@@ -1264,7 +1262,6 @@ impl<'src> Parser<'src> {
// t'{there}'
// t"""what's
// happening?"""
// "implicitly"t"concatenated"
let string_type = StringType::TString(
self.parse_interpolated_string(InterpolatedStringKind::TString)
.into(),
@@ -1281,7 +1278,7 @@ impl<'src> Parser<'src> {
match strings.len() {
// This is not possible as the function was called by matching against a
// `String` or `FStringStart` token.
// `String`, `FStringStart`, or `TStringStart` token.
0 => unreachable!("Expected to parse at least one string"),
// We need a owned value, hence the `pop` here.
1 => match strings.pop().unwrap() {
@@ -1322,58 +1319,84 @@ impl<'src> Parser<'src> {
) -> Expr {
assert!(strings.len() > 1);
let mut has_tstring = false;
let mut has_fstring = false;
let mut byte_literal_count = 0;
let mut tstring_count = 0;
for string in &strings {
match string {
StringType::FString(_) => has_fstring = true,
StringType::TString(_) => has_tstring = true,
StringType::TString(_) => tstring_count += 1,
StringType::Bytes(_) => byte_literal_count += 1,
StringType::Str(_) => {}
}
}
let has_bytes = byte_literal_count > 0;
let has_tstring = tstring_count > 0;
if has_bytes {
match byte_literal_count.cmp(&strings.len()) {
Ordering::Less => {
// TODO(dhruvmanila): This is not an ideal recovery because the parser
// replaces the byte literals with an invalid string literal node. Any
// downstream tools can extract the raw bytes from the range.
//
// We could convert the node into a string and mark it as invalid
// and would be clever to mark the type which is fewer in quantity.
if byte_literal_count < strings.len() {
// TODO(dhruvmanila): This is not an ideal recovery because the parser
// replaces the byte literals with an invalid string literal node. Any
// downstream tools can extract the raw bytes from the range.
//
// We could convert the node into a string and mark it as invalid
// and would be clever to mark the type which is fewer in quantity.
// test_err mixed_bytes_and_non_bytes_literals
// 'first' b'second'
// f'first' b'second'
// 'first' f'second' b'third'
self.add_error(
ParseErrorType::OtherError(
"Bytes literal cannot be mixed with non-bytes literals".to_string(),
),
range,
);
}
// Only construct a byte expression if all the literals are bytes
// otherwise, we'll try either string, t-string, or f-string. This is to retain
// as much information as possible.
Ordering::Equal => {
let mut values = Vec::with_capacity(strings.len());
for string in strings {
values.push(match string {
StringType::Bytes(value) => value,
_ => unreachable!("Expected `StringType::Bytes`"),
});
}
return Expr::from(ast::ExprBytesLiteral {
value: ast::BytesLiteralValue::concatenated(values),
range,
node_index: AtomicNodeIndex::dummy(),
// test_err mixed_bytes_and_non_bytes_literals
// 'first' b'second'
// f'first' b'second'
// 'first' f'second' b'third'
self.add_error(
ParseErrorType::OtherError(
"Bytes literal cannot be mixed with non-bytes literals".to_string(),
),
range,
);
}
// Only construct a byte expression if all the literals are bytes
// otherwise, we'll try either string, t-string, or f-string. This is to retain
// as much information as possible.
else {
let mut values = Vec::with_capacity(strings.len());
for string in strings {
values.push(match string {
StringType::Bytes(value) => value,
_ => unreachable!("Expected `StringType::Bytes`"),
});
}
Ordering::Greater => unreachable!(),
return Expr::from(ast::ExprBytesLiteral {
value: ast::BytesLiteralValue::concatenated(values),
range,
node_index: AtomicNodeIndex::dummy(),
});
}
}
if has_tstring {
if tstring_count < strings.len() {
self.add_error(
ParseErrorType::OtherError(
"cannot mix t-string literals with string or bytes literals".to_string(),
),
range,
);
}
// Only construct a t-string expression if all the literals are t-strings
// otherwise, we'll try either string or f-string. This is to retain
// as much information as possible.
else {
let mut values = Vec::with_capacity(strings.len());
for string in strings {
values.push(match string {
StringType::TString(value) => value,
_ => unreachable!("Expected `StringType::TString`"),
});
}
return Expr::from(ast::ExprTString {
value: ast::TStringValue::concatenated(values),
range,
node_index: AtomicNodeIndex::dummy(),
});
}
}
@@ -1414,36 +1437,17 @@ impl<'src> Parser<'src> {
});
}
if has_tstring {
let mut parts = Vec::with_capacity(strings.len());
for string in strings {
match string {
StringType::TString(tstring) => parts.push(ast::TStringPart::TString(tstring)),
StringType::FString(fstring) => {
parts.push(ruff_python_ast::TStringPart::FString(fstring));
}
StringType::Str(string) => parts.push(ast::TStringPart::Literal(string)),
StringType::Bytes(bytes) => parts.push(ast::TStringPart::Literal(
ast::StringLiteral::invalid(bytes.range()),
)),
}
}
return Expr::from(ast::ExprTString {
value: ast::TStringValue::concatenated(parts),
range,
node_index: AtomicNodeIndex::dummy(),
});
}
let mut parts = Vec::with_capacity(strings.len());
for string in strings {
match string {
StringType::FString(fstring) => parts.push(ast::FStringPart::FString(fstring)),
StringType::TString(_) => {
unreachable!("expected no tstring parts by this point")
}
StringType::Str(string) => parts.push(ast::FStringPart::Literal(string)),
// Bytes and Template strings are invalid at this point
// and stored as invalid string literal parts in the
// f-string
StringType::TString(tstring) => parts.push(ast::FStringPart::Literal(
ast::StringLiteral::invalid(tstring.range()),
)),
StringType::Bytes(bytes) => parts.push(ast::FStringPart::Literal(
ast::StringLiteral::invalid(bytes.range()),
)),

View File

@@ -13,18 +13,16 @@ expression: suite
range: 0..3,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..3,
node_index: AtomicNodeIndex(..),
elements: [],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..3,
node_index: AtomicNodeIndex(..),
elements: [],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -1,64 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..18,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..18,
value: TStringValue {
inner: Concatenated(
[
FString(
FString {
range: 0..9,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..8,
node_index: AtomicNodeIndex(..),
value: "Hello ",
},
),
],
flags: FStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..18,
}

View File

@@ -1,76 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..22,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..22,
value: TStringValue {
inner: Concatenated(
[
FString(
FString {
range: 0..9,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..8,
node_index: AtomicNodeIndex(..),
value: "Hello ",
},
),
],
flags: FStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
Literal(
StringLiteral {
range: 19..22,
node_index: AtomicNodeIndex(..),
value: "!",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..22,
}

View File

@@ -1,56 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..17,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..17,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..17,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..17,
}

View File

@@ -1,56 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..17,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..17,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..17,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..17,
}

View File

@@ -1,85 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..22,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..22,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..22,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
Interpolation(
InterpolatedElement {
range: 16..21,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 17..20,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 17..20,
node_index: AtomicNodeIndex(..),
value: "!",
flags: StringLiteralFlags {
quote_style: Double,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..22,
}

View File

@@ -1,97 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..31,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..31,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..8,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
TString(
TString {
range: 9..22,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 11..16,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
Interpolation(
InterpolatedElement {
range: 16..21,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 17..20,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 17..20,
node_index: AtomicNodeIndex(..),
value: "!",
flags: StringLiteralFlags {
quote_style: Double,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
Literal(
StringLiteral {
range: 23..31,
node_index: AtomicNodeIndex(..),
value: "again!",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..31,
}

View File

@@ -13,60 +13,58 @@ expression: suite
range: 0..18,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..18,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..5,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("a"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Interpolation(
InterpolatedElement {
range: 5..10,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 7..8,
id: Name("b"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 10..17,
node_index: AtomicNodeIndex(..),
value: "{foo}",
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..18,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..5,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("a"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Interpolation(
InterpolatedElement {
range: 5..10,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 7..8,
id: Name("b"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 10..17,
node_index: AtomicNodeIndex(..),
value: "{foo}",
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,57 +13,55 @@ expression: suite
range: 0..13,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..11,
left: NumberLiteral(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..11,
left: NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 3..5,
value: Int(
42,
),
},
),
ops: [
Eq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 3..5,
range: 9..11,
value: Int(
42,
),
},
),
ops: [
Eq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 9..11,
value: Int(
42,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,87 +13,85 @@ expression: suite
range: 0..16,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..16,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..15,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..14,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..14,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..13,
value: StringLiteralValue {
inner: Concatenated(
ConcatenatedStringLiteral {
strings: [
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
TString {
range: 0..16,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..15,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..14,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..14,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..13,
value: StringLiteralValue {
inner: Concatenated(
ConcatenatedStringLiteral {
strings: [
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
StringLiteral {
range: 11..13,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
StringLiteral {
range: 11..13,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
],
value: "",
},
),
},
},
],
value: "",
},
),
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,60 +13,58 @@ expression: suite
range: 0..15,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..15,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..14,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 8..12,
id: Name("spec"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..15,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..14,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 8..12,
id: Name("spec"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,72 +13,70 @@ expression: suite
range: 0..13,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..10,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
expression: StringLiteral(
ExprStringLiteral {
node_index: AtomicNodeIndex(..),
range: 8..10,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 8..10,
node_index: AtomicNodeIndex(..),
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
),
},
},
),
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,57 +13,55 @@ expression: suite
range: 0..11,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..10,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..9,
left: NumberLiteral(
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..10,
node_index: AtomicNodeIndex(..),
expression: Compare(
ExprCompare {
node_index: AtomicNodeIndex(..),
range: 3..9,
left: NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 3..4,
value: Int(
1,
),
},
),
ops: [
NotEq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 3..4,
range: 8..9,
value: Int(
1,
2,
),
},
),
ops: [
NotEq,
],
comparators: [
NumberLiteral(
ExprNumberLiteral {
node_index: AtomicNodeIndex(..),
range: 8..9,
value: Int(
2,
),
},
),
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
],
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,50 +13,48 @@ expression: suite
range: 0..13,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
value: "spec",
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..13,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..12,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..6,
id: Name("foo"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 7..11,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 7..11,
node_index: AtomicNodeIndex(..),
value: "spec",
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,41 +13,39 @@ expression: suite
range: 0..10,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: " =",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: " =",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,41 +13,39 @@ expression: suite
range: 0..10,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "= ",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..4,
id: Name("x"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "= ",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,35 +13,33 @@ expression: suite
range: 0..10,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Yield(
ExprYield {
node_index: AtomicNodeIndex(..),
range: 3..8,
value: None,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Yield(
ExprYield {
node_index: AtomicNodeIndex(..),
range: 3..8,
value: None,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -1,56 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..18,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..18,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..9,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Unicode,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..18,
}

View File

@@ -1,68 +0,0 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
[
Expr(
StmtExpr {
node_index: AtomicNodeIndex(..),
range: 0..22,
value: TString(
ExprTString {
node_index: AtomicNodeIndex(..),
range: 0..22,
value: TStringValue {
inner: Concatenated(
[
Literal(
StringLiteral {
range: 0..9,
node_index: AtomicNodeIndex(..),
value: "Hello ",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Unicode,
triple_quoted: false,
},
},
),
TString(
TString {
range: 10..18,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 12..17,
node_index: AtomicNodeIndex(..),
value: "world",
},
),
],
flags: TStringFlags {
quote_style: Single,
prefix: Regular,
triple_quoted: false,
},
},
),
Literal(
StringLiteral {
range: 19..22,
node_index: AtomicNodeIndex(..),
value: "!",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
],
),
},
},
),
},
),
]

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/string.rs
expression: suite
---
ParseError {
error: OtherError(
"cannot mix t-string literals with string or bytes literals",
),
location: 0..22,
}

View File

@@ -13,38 +13,36 @@ expression: suite
range: 0..7,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..7,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 3..6,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 4..5,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
TString {
range: 0..7,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 3..6,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 4..5,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
triple_quoted: false,
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
},
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,38 +13,36 @@ expression: suite
range: 0..11,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 5..8,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..7,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 5..8,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..7,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
triple_quoted: true,
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
},
triple_quoted: true,
},
),
},
),
},
},

View File

@@ -13,74 +13,72 @@ expression: suite
range: 0..22,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..22,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..5,
node_index: AtomicNodeIndex(..),
value: "aaa",
},
),
Interpolation(
InterpolatedElement {
range: 5..10,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..9,
id: Name("bbb"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 10..13,
node_index: AtomicNodeIndex(..),
value: "ccc",
},
),
Interpolation(
InterpolatedElement {
range: 13..18,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 14..17,
id: Name("ddd"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 18..21,
node_index: AtomicNodeIndex(..),
value: "eee",
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..22,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..5,
node_index: AtomicNodeIndex(..),
value: "aaa",
},
),
Interpolation(
InterpolatedElement {
range: 5..10,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..9,
id: Name("bbb"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 10..13,
node_index: AtomicNodeIndex(..),
value: "ccc",
},
),
Interpolation(
InterpolatedElement {
range: 13..18,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 14..17,
id: Name("ddd"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 18..21,
node_index: AtomicNodeIndex(..),
value: "eee",
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,43 +13,41 @@ expression: suite
range: 0..8,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..8,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..4,
node_index: AtomicNodeIndex(..),
value: "\\",
},
),
Interpolation(
InterpolatedElement {
range: 4..7,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 5..6,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..8,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..4,
node_index: AtomicNodeIndex(..),
value: "\\",
},
),
Interpolation(
InterpolatedElement {
range: 4..7,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 5..6,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,43 +13,41 @@ expression: suite
range: 0..8,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..8,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..4,
node_index: AtomicNodeIndex(..),
value: "\n",
},
),
Interpolation(
InterpolatedElement {
range: 4..7,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 5..6,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..8,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..4,
node_index: AtomicNodeIndex(..),
value: "\n",
},
),
Interpolation(
InterpolatedElement {
range: 4..7,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 5..6,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,45 +13,43 @@ expression: suite
range: 0..9,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..9,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 3..5,
node_index: AtomicNodeIndex(..),
value: "\\\n",
},
),
Interpolation(
InterpolatedElement {
range: 5..8,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..7,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
TString {
range: 0..9,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 3..5,
node_index: AtomicNodeIndex(..),
value: "\\\n",
},
triple_quoted: false,
),
Interpolation(
InterpolatedElement {
range: 5..8,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..7,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
},
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,41 +13,39 @@ expression: suite
range: 0..10,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..7,
id: Name("user"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..10,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..9,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..7,
id: Name("user"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,77 +13,75 @@ expression: suite
range: 0..38,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..38,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..6,
node_index: AtomicNodeIndex(..),
value: "mix ",
},
),
Interpolation(
InterpolatedElement {
range: 6..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 7..11,
id: Name("user"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 13..28,
node_index: AtomicNodeIndex(..),
value: " with text and ",
},
),
Interpolation(
InterpolatedElement {
range: 28..37,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 29..35,
id: Name("second"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..38,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 2..6,
node_index: AtomicNodeIndex(..),
value: "mix ",
},
),
Interpolation(
InterpolatedElement {
range: 6..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 7..11,
id: Name("user"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
Literal(
InterpolatedStringLiteralElement {
range: 13..28,
node_index: AtomicNodeIndex(..),
value: " with text and ",
},
),
Interpolation(
InterpolatedElement {
range: 28..37,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 29..35,
id: Name("second"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,55 +13,53 @@ expression: suite
range: 0..14,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..14,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..7,
id: Name("user"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 9..12,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 9..12,
node_index: AtomicNodeIndex(..),
value: ">10",
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 0..14,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 2..13,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 3..7,
id: Name("user"),
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: Some(
InterpolatedStringFormatSpec {
range: 9..12,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 9..12,
node_index: AtomicNodeIndex(..),
value: ">10",
},
),
],
},
),
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -13,43 +13,41 @@ expression: suite
range: 0..11,
value: TStringValue {
inner: Single(
TString(
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 4..5,
node_index: AtomicNodeIndex(..),
value: "\n",
},
),
Interpolation(
InterpolatedElement {
range: 5..8,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..7,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: true,
},
TString {
range: 0..11,
node_index: AtomicNodeIndex(..),
elements: [
Literal(
InterpolatedStringLiteralElement {
range: 4..5,
node_index: AtomicNodeIndex(..),
value: "\n",
},
),
Interpolation(
InterpolatedElement {
range: 5..8,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 6..7,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: true,
},
),
},
),
},
},

View File

@@ -850,58 +850,58 @@ mod tests {
}
#[test]
fn test_parse_t_string_concat_1() {
fn test_parse_t_string_concat_1_error() {
let source = "'Hello ' t'world'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_t_string_concat_2() {
fn test_parse_t_string_concat_2_error() {
let source = "'Hello ' t'world'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_t_string_concat_3() {
fn test_parse_t_string_concat_3_error() {
let source = "'Hello ' t'world{\"!\"}'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_t_string_concat_4() {
fn test_parse_t_string_concat_4_error() {
let source = "'Hello ' t'world{\"!\"}' 'again!'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_u_t_string_concat_1() {
fn test_parse_u_t_string_concat_1_error() {
let source = "u'Hello ' t'world'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_u_t_string_concat_2() {
fn test_parse_u_t_string_concat_2_error() {
let source = "u'Hello ' t'world' '!'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_f_t_string_concat_1() {
fn test_parse_f_t_string_concat_1_error() {
let source = "f'Hello ' t'world'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_f_t_string_concat_2() {
fn test_parse_f_t_string_concat_2_error() {
let source = "f'Hello ' t'world' '!'";
let suite = parse_suite(source).unwrap();
let suite = parse_suite(source).unwrap_err();
insta::assert_debug_snapshot!(suite);
}

View File

@@ -66,36 +66,34 @@ Module(
range: 10..19,
value: TStringValue {
inner: Single(
TString(
TString {
range: 10..19,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 12..18,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 13..14,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: Str,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 10..19,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 12..18,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 13..14,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: Str,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -20,36 +20,34 @@ Module(
range: 44..49,
value: TStringValue {
inner: Single(
TString(
TString {
range: 44..49,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 46..48,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 47..47,
id: Name(""),
ctx: Invalid,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 44..49,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 46..48,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 47..47,
id: Name(""),
ctx: Invalid,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},
@@ -66,36 +64,34 @@ Module(
range: 50..57,
value: TStringValue {
inner: Single(
TString(
TString {
range: 50..57,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 52..56,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 53..53,
id: Name(""),
ctx: Invalid,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 50..57,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 52..56,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 53..53,
id: Name(""),
ctx: Invalid,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -20,36 +20,34 @@ Module(
range: 44..52,
value: TStringValue {
inner: Single(
TString(
TString {
range: 44..52,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 46..51,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 47..48,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 44..52,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 46..51,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 47..48,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

View File

@@ -20,36 +20,34 @@ Module(
range: 44..54,
value: TStringValue {
inner: Single(
TString(
TString {
range: 44..54,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 46..53,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 47..48,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 44..54,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 46..53,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 47..48,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},
@@ -66,36 +64,34 @@ Module(
range: 55..65,
value: TStringValue {
inner: Single(
TString(
TString {
range: 55..65,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 57..64,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 58..59,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
TString {
range: 55..65,
node_index: AtomicNodeIndex(..),
elements: [
Interpolation(
InterpolatedElement {
range: 57..64,
node_index: AtomicNodeIndex(..),
expression: Name(
ExprName {
node_index: AtomicNodeIndex(..),
range: 58..59,
id: Name("x"),
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: TStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
),
},
),
},
},

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