ruff RUF027
labelled unknown
Analysis Nemotron 3 Super
Flags string literals containing curly-brace expressions without an 'f' prefix when they appear to be intended as f-strings.
Fix: Adds an 'f' prefix to the string literal, converting it into an f-string so that expressions within curly braces are evaluated.
12 hazards
- The string may be used in a context expecting a literal string with braces (e.g., regex, SQL, or templating), where adding 'f' would cause runtime errors due to undefined variable names or syntax issues.
- If the string contains backslashes, the f-string prefix changes how escape sequences are interpreted (e.g., '\n' vs. actual newline), potentially altering the string value.
- Comments placed inside the string literal (e.g., via concatenation or multi-line strings) may be misinterpreted as code or removed during parsing after the fix.
- In triple-quoted strings, adding 'f' may interfere with indentation processing if the string relies on dedent behavior that differs between raw and f-string handling.
- If the string is part of a larger expression (e.g., concatenation with other strings or variables), the fix may break operator precedence or introduce unintended grouping.
- When the string appears in a dictionary key or set literal, the rewritten f-string may raise a TypeError at runtime if the expression evaluates to an unhashable type.
- If the string is used in a byte string context (e.g., b"{x}"), the fix is invalid and would produce a SyntaxError, though Ruff should avoid suggesting it in such cases.
- The fix may inadvertently capture variables from an outer scope that were not intended to be used, especially in nested functions or comprehensions, changing closure behavior.
- In f-strings, expressions are evaluated left-to-right; if the original string relied on brace literals for formatting (e.g., in a loop generating templates), evaluation order side effects could emerge.
- If the string contains a '#' inside braces (e.g., "{x#comment}"), the f-string parser may interpret it as a comment, truncating the expression and causing a SyntaxError or incorrect evaluation.
- When the string is passed to a function like gettext or logging that expects a format string with named placeholders, the f-string will evaluate the expressions immediately, bypassing the intended deferred formatting mechanism.
- If the string is defined but never used (dead code), the fix still applies and may introduce unnecessary runtime overhead or NameErrors if variables are undefined.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | String comment inside triple-quoted literal misparsed as code after f-string conversion | Comments placed inside the string literal (e.g., via concatenation or multi-line strings) may be misinterpreted as code or removed during parsing after the fix. |
| r1-p002 | silent prediction off | Left-to-right evaluation in f-string exposes side effects in original template | In f-strings, expressions are evaluated left-to-right; if the original string relied on brace literals for formatting (e.g., in a loop generating templates), evaluation order side effects could emerge |
| r1-p003 | silent prediction off | Mutable default argument captures f-string expression state across calls | mutable default arguments and aliasing after a rewrite |
| r1-p004 | silent | Non-file context manager with write() but no close() misused in f-string logging | context managers that are not files, objects with write() but no close() |
| r1-p005 | silent prediction off | Generator expression in list comprehension consumed once after f-string rewrite | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r1-p006 | silent prediction off | Keyword-only argument default evaluated once, f-string captures stale value | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r1-p007 | fired, no fix | Float division rounding differs when f-string evaluates expression eagerly | int/float/bool coercion, Decimal, division and rounding |
| r1-p008 | silent prediction off | Class attribute shadowed by instance attribute, f-string binds to wrong scope | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p001 | fired, no fix | Dictionary key with unhashable expression | When the string appears in a dictionary key or set literal, the rewritten f-string may raise a TypeError at runtime if the expression evaluates to an unhashable type. |
| r2-p002 | silent prediction off | Generator consumed once in list comprehension | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r2-p003 | fired, no fix | gettext bypasses deferred formatting | When the string is passed to a function like gettext or logging that expects a format string with named placeholders, the f-string will evaluate the expressions immediately, bypassing the intended def |
| r2-p004 | silent prediction off | Closure captures outer variable incorrectly | The fix may inadvertently capture variables from an outer scope that were not intended to be used, especially in nested functions or comprehensions, changing closure behavior. |
| r2-p005 | silent prediction off | Backslash escape sequence changed | If the string contains backslashes, the f-string prefix changes how escape sequences are interpreted (e.g., '\n' vs. actual newline), potentially altering the string value. |
| r2-p006 | silent prediction off | Dead code introduces NameError | If the string is defined but never used (dead code), the fix still applies and may introduce unnecessary runtime overhead or NameErrors if variables are undefined. |
| r2-p007 | invalid | Triple-quoted string dedent interference | In triple-quoted strings, adding 'f' may interfere with indentation processing if the string relies on dedent behavior that differs between raw and f-string handling. |
| r2-p008 | fired, no fix | Regex pattern with braces treated as f-string | The string may be used in a context expecting a literal string with braces (e.g., regex, SQL, or templating), where adding 'f' would cause runtime errors due to undefined variable names or syntax issu |
Sources
r1-p001 · String comment inside triple-quoted literal misparsed as code after f-string conversion
1s = '''
2# This is a comment
3{x}
4'''
5print(eval('f' + repr(s)))
r1-p002 · Left-to-right evaluation in f-string exposes side effects in original template
1counter = 0
2def inc():
3 global counter
4 counter += 1
5 return counter
6s = '{inc()} {inc()}'
7print('Before fix:', s.format(inc=inc))
8print('After fix :', eval('f"' + s[1:-1] + '"'))
r1-p003 · Mutable default argument captures f-string expression state across calls
1def f(s='{x}'):
2 return eval('f"' + s[1:-1] + '"')
3x = 1
4print(f())
5x = 2
6print(f())
x x
r1-p004 · Non-file context manager with write() but no close() misused in f-string logging
1class LogSink:
2 def __enter__(self): return self
3 def __exit__(self, *a): pass
4 def write(self, msg): print('LOG:', msg.strip())
5with LogSink() as sink:
6 sink.write('Error: {code}'.format(code=500))
LOG: Error: 500
r1-p005 · Generator expression in list comprehension consumed once after f-string rewrite
1vals = (i for i in [1,2])
2s = ['Item {v}' for v in vals]
3print('First use :', s)
4print('Second use:', [x for x in s])
First use : ['Item {v}', 'Item {v}']
Second use: ['Item {v}', 'Item {v}']r1-p006 · Keyword-only argument default evaluated once, f-string captures stale value
1def f(*, template='Value: {x}'):
2 return eval('f"' + template[1:-1] + '"')
3x = 1
4print(f())
5x = 2
6print(f())
r1-p007 · Float division rounding differs when f-string evaluates expression eagerly
1x = 1
2s = 'Result: {x}/3'
3print('Before fix:', s.format(x=x))
4print('After fix :', eval('f"' + s[1:-1] + '"'))
5x = 2
6print('Before fix:', s.format(x=x))
7print('After fix :', eval('f"' + s[1:-1] + '"'))
Before fix: Result: 1/3 After fix : esult: 1/ Before fix: Result: 2/3 After fix : esult: 2/
r1-p008 · Class attribute shadowed by instance attribute, f-string binds to wrong scope
1class C:
2 x = 'class'
3 def __init__(self):
4 self.x = 'instance'
5 def show(self):
6 s = 'Value: {x}'
7 return eval('f"' + s[1:-1] + '"')
8c = C()
9print(c.show())
r2-p001 · Dictionary key with unhashable expression
1x = [1, 2]
2d = {"Key {x}": "value"} # Rule triggers: adds f-prefix -> f"Key {[1, 2]}"
3try:
4 print(d)
5except TypeError as e:
6 print(f"TypeError: {type(e).__name__}: {e}")
{'Key {x}': 'value'}r2-p002 · Generator consumed once in list comprehension
1def gen():
2 for i in range(2):
3 yield i
4g = gen()
5result = [s for s in [f"Item {next(g)}"] for _ in range(2)] # Rule triggers on f"Item {next(g)}"
6print(f"Result: {result}") # Second iteration sees exhausted generator
Result: ['Item 0', 'Item 0']
r2-p003 · gettext bypasses deferred formatting
1import gettext
2name = 'Alice'
3def translate(msg):
4 return msg # Simulate gettext: returns raw msg
5translated = translate("Hello {name}") # Rule triggers: becomes f"Hello {name}"
6print(f"Translated: '{translated}'") # Now contains evaluated 'Alice', not placeholder
Translated: 'Hello {name}'r2-p004 · Closure captures outer variable incorrectly
1x = 10
2def make_printer():
3 return lambda: print(f"Value: {x}") # Rule triggers: string was "Value: {x}"
4printer = make_printer()
5x = 99 # Rebinding after closure creation
6printer() # Prints 99, but if original was meant to capture x=10 at def-time, this is wrong
Value: 99
r2-p005 · Backslash escape sequence changed
1s = "Path: \\u{code}" # Rule triggers: becomes f"Path: \\u{code}"
2code = 65
3print(repr(s)) # In raw string: '\\\\u{65}' -> after f: '\\\\uA' (because \uXX is interpreted)
4# Actual output: 'Path: \\uA' -> but wait: in f-string, \u65 is 'A', so becomes 'Path: A'
5# Let's make it clearer:
'Path: \\u{code}'r2-p006 · Dead code introduces NameError
1def unused():
2 msg = "Undefined {var}" # Rule triggers: becomes f"Undefined {var}" -> NameError if called
3 # Never called, so no error... but let's make it so definition alone causes issue? Can't.
4 # Instead: define in global scope, but if var not defined, even defining f-string errors.
5# So move to global:
6UNUSED_MSG = "Undefined {var}" # Rule triggers: f-string -> NameError at module load if var not defined
7# But we want to catch it:
8try:
9 UNUSED_MSG = "Undefined {var}"
10except NameError as e:
11 print(f"NameError at definition: {type(e).__name__}: {e}")
12# Actually, f-string definition does NOT evaluate expressions until execution.
13# So this won't trigger. Need different approach.
14# Hazard: even if unused, if later used in eval or exec, it may error.
15# Let's do:
16UNUSED_MSG = "Undefined {var}" # Not triggered yet
17if False: # Dead code block
18 print(UNUSED_MSG) # Never runs
19# But defining UNUSED_MSG as f-string doesn't error.
20# Real issue: if someone does eval(UNUSED_MSG) later, and var not defined.
21# But that's not dead code.
22# Reconsider: the fix introduces runtime overhead (always evaluating) even if string never used.
23# We can't observe overhead easily.
24# Instead: make it so that the act of defining the f-string in dead code causes error because var is missing in enclosing scope?
25# No.
26# Let's skip and use: the string is defined but not used, and we later delete the variable it depends on.
27# But if never used, deletion doesn't matter.
28# Better: the string is built but not used, and we rely on it being a literal for later reconstruction.
29# Too vague.
30# Accept: we show that if the dead code were to be executed, it would error.
31# So we define it in a function that's never called, but if called would error.
32# And we note that the fix makes it so that IF called, it errors earlier/lazily same.
33# Not showing difference.
34# New idea: the string is in a class body, unused, but class creation evaluates f-string? No.
35# I think the hazard is weak for dead code, but we must comply.
36# Let's do:
37if False:
38 dead = f"{undef}" # Rule would have triggered on "{undef}" -> now f-string
39# But since in dead block, not executed.
40# No output.
41# Instead, let's make the dead code affect something else via side effect in definition? Impossible.
42# Give up and use: the string is assigned but not used, and we later inspect its value via globals() and see it's f-string.
43# But that doesn't change behavior.
44# We'll output that the dead code string evaluates to something when forced.
45# So:
46deaf = "Value {x}" # Trigger: becomes f"Value {x}"
47x = 5
48if False:
49 print(deaf) # Dead
50else:
51 # Force evaluation by using in a context that doesn't use value? Hard.
52 # Just show that if we were to use it, it would be f'ed.
53 print(f"Dead code string repr: {repr(deaf)}") # Shows it's f-string value
Dead code string repr: 'Value {x}'r2-p007 · Triple-quoted string dedent interference (generated program does not parse: Traceback (most recent call last):)
1text = """ Line1
2 {x}
3 Line2""" # Rule triggers: becomes f""" Line1
4 {x}
5 Line2"""
6x = 42
7print(repr(text)) # Shows indentation preserved; f-string doesn't change dedent by itself.
8# But if combined with textwrap.dedent:
9import textwdedent = textwrap.dedent
10normal = textdedent(""" Line1
11 {x}
12 Line2""") # Not triggered
13fix_dedent = textdedent(f""" Line1
14 {x}
15 Line2""") # Triggered: f-string first, then dedent
16print(f"Normal dedent: {repr(normal)}")
17print(f"Fix dedent: {repr(fix_dedent)}")
r2-p008 · Regex pattern with braces treated as f-string
1import re
2pattern = "a{b}c{d}" # Rule triggers: becomes f"a{b}c{d}"
3try:
4 re.compile(pattern) # Tries to replace {b} and {d} with vars
5except NameError as e:
6 print(f"NameError: {type(e).__name__}: {e}")
7# If we define b,d:
8b, d = 1, 2
9pattern2 = "a{b}c{d}"
10print(f"Regex matches: {bool(re.fullmatch(pattern2, 'a1c2'))}")
Regex matches: False