Skip to content

FURB116 has false positives and unsafe fixes #16472

@dscorbett

Description

@dscorbett

Summary

The fix for f-string-number-format (FURB116) only works on non-negative integers. If the argument isn’t an int or bool literal (or a variable that was set to one of those, or any other expression that Ruff can prove is a non-negative integer), the rule should be marked unsafe. The fix can also introduce a syntax error for certain complex expressions that the rule was probably not intended to handle.

The fix changes behavior for negative integers.

$ cat >furb116_1.py <<'# EOF'
x = -1
print(bin(x)[2:])
# EOF

$ python furb116_1.py
b1

$ ruff --isolated check --preview --select FURB116 furb116_1.py --fix
Found 1 error (1 fixed, 0 remaining).

$ cat furb116_1.py
x = -1
print(f"{x:b}")

$ python furb116_1.py
-1

FURB116 applies to all number literals, but float and complex literals are false positives which the rule should ignore.

$ cat >furb116_2.py <<'# EOF'
bin(1.0)[2:]
# EOF

$ python furb116_2.py 2>&1 | tail -n 1
TypeError: 'float' object cannot be interpreted as an integer

$ ruff --isolated check --preview --select FURB116 furb116_2.py --fix
Found 1 error (1 fixed, 0 remaining).

$ python furb116_2.py 2>&1 | tail -n 1
ValueError: Unknown format code 'b' for object of type 'float'

In general, the fix should be marked unsafe, except when it can be proved to be safe. Otherwise, the fix changes behavior, sometimes by changing the error type as in the previous example, and sometimes by changing behavior in other ways.

$ cat >furb116_3.py <<'# EOF'
import datetime
d = datetime.datetime.now(tz=datetime.UTC)
print(bin(d)[2:])
# EOF

$ python furb116_3.py 2>&1 | tail -n 1
TypeError: 'datetime.datetime' object cannot be interpreted as an integer

$ ruff --isolated check --preview --select FURB116 furb116_3.py --fix
Found 1 error (1 fixed, 0 remaining).

$ cat furb116_3.py
import datetime
d = datetime.datetime.now(tz=datetime.UTC)
print(f"{d:b}")

$ python furb116_3.py
b

The fix introduces a syntax error when the argument begins with a brace.

$ cat >furb116_4.py <<'# EOF'
print(bin({0: 1}[0].numerator)[2:])
# EOF

$ python furb116_4.py
1

$ ruff --isolated check --preview --select FURB116 furb116_4.py --diff 2>&1 | grep error:
error: Fix introduced a syntax error. Reverting all changes.

The fix introduces a syntax error in Python 3.11 and earlier when the argument contains the same kind of quotation marks as the fix’s f-string.

$ cat >furb116_5.py <<'# EOF'
print(bin(len("xyz").numerator)[2:])
# EOF

$ python3.11 furb116_5.py
11

$ ruff --isolated check --target-version py311 --preview --select FURB116 furb116_5.py --fix
Found 1 error (1 fixed, 0 remaining).

$ cat furb116_5.py
print(f"{len("xyz").numerator:b}")

$ python3.11 furb116_5.py 2>&1 | tail -n 1
SyntaxError: f-string: unmatched '('

The fix introduces a syntax error in Python 3.11 and earlier when the argument contains a backslash.

$ cat >furb116_6.py <<'# EOF'
print(bin(ord("\\").numerator)[2:])
# EOF

$ python3.11 furb116_6.py
1011100

$ ruff --isolated check --target-version py311 --preview --select FURB116 furb116_6.py --fix
Found 1 error (1 fixed, 0 remaining).

$ cat furb116_6.py
print(f"{ord("\\").numerator:b}")

$ python3.11 furb116_6.py 2>&1 | tail -n 1
SyntaxError: unexpected character after line continuation character

The fix introduces a syntax error in Python 3.11 and earlier when the argument spans multiple lines.

$ cat >furb116_7.py <<'# EOF'
import sys
print(hex(sys
.maxunicode)[2:])
# EOF

$ python3.11 furb116_7.py
10ffff

$ ruff --isolated check --target-version py311 --preview --select FURB116 furb116_7.py --fix
Found 1 error (1 fixed, 0 remaining).

$ cat furb116_7.py
import sys
print(f"{sys
.maxunicode:x}")

$ python3.11 furb116_7.py 2>&1 | tail -n 1
SyntaxError: unterminated string literal (detected at line 2)

Version

ruff 0.9.9 (091d0af 2025-02-28)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfixesRelated to suggested fixes for violationshelp wantedContributions especially welcome

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions