Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid byte-string conversions #1618

Merged
merged 1 commit into from Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions resources/test/fixtures/pyupgrade/UP018.py
Expand Up @@ -7,12 +7,14 @@
str("foo", encoding="UTF-8")
str("foo"
"bar")
str(b"foo")
bytes("foo", encoding="UTF-8")
bytes(*a)
bytes("foo", *a)
bytes("foo", **a)
bytes(b"foo"
b"bar")
bytes("foo")

# These become string or byte literals
str()
Expand Down
65 changes: 48 additions & 17 deletions src/pyupgrade/plugins/native_literals.rs
Expand Up @@ -17,21 +17,31 @@ pub fn native_literals(
) {
let ExprKind::Name { id, .. } = &func.node else { return; };

if (id == "str" || id == "bytes")
&& keywords.is_empty()
&& args.len() <= 1
&& checker.is_builtin(id)
{
if !keywords.is_empty() || args.len() > 1 {
return;
}

if (id == "str" || id == "bytes") && checker.is_builtin(id) {
let Some(arg) = args.get(0) else {
let literal_type = if id == "str" {
let mut check = Check::new(CheckKind::NativeLiterals(if id == "str" {
LiteralType::Str
} else {
LiteralType::Bytes
};
let mut check = Check::new(CheckKind::NativeLiterals(literal_type), Range::from_located(expr));
}), Range::from_located(expr));
if checker.patch(&CheckCode::UP018) {
check.amend(Fix::replacement(
format!("{}\"\"", if id == "bytes" { "b" } else { "" }),
if id == "bytes" {
let mut content = String::with_capacity(3);
content.push('b');
content.push(checker.style.quote().into());
content.push(checker.style.quote().into());
content
} else {
let mut content = String::with_capacity(2);
content.push(checker.style.quote().into());
content.push(checker.style.quote().into());
content
},
expr.location,
expr.end_location.unwrap(),
));
Expand All @@ -40,14 +50,31 @@ pub fn native_literals(
return;
};

let ExprKind::Constant { value, ..} = &arg.node else {
// Look for `str("")`.
if id == "str"
&& !matches!(
&arg.node,
ExprKind::Constant {
value: Constant::Str(_),
..
},
)
{
return;
};
let literal_type = match value {
Constant::Str { .. } => LiteralType::Str,
Constant::Bytes { .. } => LiteralType::Bytes,
_ => return,
};
}

// Look for `bytes(b"")`
if id == "bytes"
&& !matches!(
&arg.node,
ExprKind::Constant {
value: Constant::Bytes(_),
..
},
)
{
return;
}

// rust-python merges adjacent string/bytes literals into one node, but we can't
// safely remove the outer call in this situation. We're following pyupgrade
Expand All @@ -65,7 +92,11 @@ pub fn native_literals(
}

let mut check = Check::new(
CheckKind::NativeLiterals(literal_type),
CheckKind::NativeLiterals(if id == "str" {
LiteralType::Str
} else {
LiteralType::Bytes
}),
Range::from_located(expr),
);
if checker.patch(&CheckCode::UP018) {
Expand Down
48 changes: 24 additions & 24 deletions src/pyupgrade/snapshots/ruff__pyupgrade__tests__UP018_UP018.py.snap
Expand Up @@ -5,103 +5,103 @@ expression: checks
- kind:
NativeLiterals: Str
location:
row: 18
row: 20
column: 0
end_location:
row: 18
row: 20
column: 5
fix:
content: "\"\""
location:
row: 18
row: 20
column: 0
end_location:
row: 18
row: 20
column: 5
parent: ~
- kind:
NativeLiterals: Str
location:
row: 19
row: 21
column: 0
end_location:
row: 19
row: 21
column: 10
fix:
content: "\"foo\""
location:
row: 19
row: 21
column: 0
end_location:
row: 19
row: 21
column: 10
parent: ~
- kind:
NativeLiterals: Str
location:
row: 20
row: 22
column: 0
end_location:
row: 21
row: 23
column: 7
fix:
content: "\"\"\"\nfoo\"\"\""
location:
row: 20
row: 22
column: 0
end_location:
row: 21
row: 23
column: 7
parent: ~
- kind:
NativeLiterals: Bytes
location:
row: 22
row: 24
column: 0
end_location:
row: 22
row: 24
column: 7
fix:
content: "b\"\""
location:
row: 22
row: 24
column: 0
end_location:
row: 22
row: 24
column: 7
parent: ~
- kind:
NativeLiterals: Bytes
location:
row: 23
row: 25
column: 0
end_location:
row: 23
row: 25
column: 13
fix:
content: "b\"foo\""
location:
row: 23
row: 25
column: 0
end_location:
row: 23
row: 25
column: 13
parent: ~
- kind:
NativeLiterals: Bytes
location:
row: 24
row: 26
column: 0
end_location:
row: 25
row: 27
column: 7
fix:
content: "b\"\"\"\nfoo\"\"\""
location:
row: 24
row: 26
column: 0
end_location:
row: 25
row: 27
column: 7
parent: ~

19 changes: 19 additions & 0 deletions src/source_code_style.rs
@@ -1,5 +1,6 @@
//! Detect code style from Python source code.

use std::fmt;
use std::ops::Deref;

use once_cell::unsync::OnceCell;
Expand Down Expand Up @@ -69,6 +70,24 @@ impl From<&Quote> for vendor::str::Quote {
}
}

impl fmt::Display for Quote {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Quote::Single => write!(f, "\'"),
Quote::Double => write!(f, "\""),
}
}
}

impl From<&Quote> for char {
fn from(val: &Quote) -> Self {
match val {
Quote::Single => '\'',
Quote::Double => '"',
}
}
}

/// The indentation style used in Python source code.
#[derive(Debug, PartialEq, Eq)]
pub struct Indentation(String);
Expand Down