Skip to content

Fix awkward line splits from JuliaFormatter #1306

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

Merged

Conversation

ChrisRackauckas
Copy link
Member

Summary

This PR fixes awkward line splits that were introduced by JuliaFormatter, making the code more readable by keeping logical units together.

Changes Made

1. Fixed tuple assignments in plotting functions

  • fig, ax, plt = ... and fig, ax, hm = ... now stay on single lines
  • Files affected:
    • ext/CatalystCairoMakieExtension/cairo_makie_extension_spatial_modelling.jl
    • ext/CatalystGraphMakieExtension/graph_makie_extension_spatial_modelling.jl

2. Fixed constant definitions

  • Tuples defining constants now stay on single lines when reasonable
  • Files affected:
    • src/reactionsystem_serialisation/serialise_fields.jl (CONTINUOUS_EVENTS_FS, DISCRETE_EVENTS_FS, CONNECTION_TYPE_FS)
    • src/reaction.jl (NON_CONSTANT_JUMP_SCALES)

3. Fixed split function calls

  • Function calls with their arguments stay together
  • Files affected:
    • src/reactionsystem_serialisation/serialise_fields.jl (dependency_split! calls)
    • src/dsl.jl (read_equations_option!, read_observables_option)

4. Improved readability of long lines

  • Binary operations with warnings kept more readable
  • Arrays and sets formatted more sensibly
  • Files affected:
    • src/chemistry_functionality.jl
    • src/dsl.jl (bwd_arrows, option_keys)

Verification

These changes have been tested with an updated version of JuliaFormatter (PR pending) to ensure they won't be reverted. The updated formatter is more conservative about breaking lines, especially for:

  • Tuple assignments on the LHS of =
  • Function calls that are only slightly over the margin
  • Constant definitions

Related Issues

This addresses formatting issues similar to those seen in:

🤖 Generated with Claude Code

- Keep tuple assignments (fig, ax, plt) on single lines
- Fix split function calls and constant definitions
- Improve readability by keeping related elements together
- Binary operations with long warnings kept more readable

These changes address formatting issues introduced by JuliaFormatter
that made the code less readable by splitting logical units across lines.
@ChrisRackauckas
Copy link
Member Author

Background on this formatting issue

This PR addresses a long-standing issue with JuliaFormatter that has affected multiple SciML packages. Here's the comprehensive background:

The Problem

JuliaFormatter has been overly aggressive in breaking lines, particularly with:

  1. Tuple assignments on the LHS of = - Breaking fig, ax, plt = into multiple lines
  2. Function calls that are only slightly over the margin - Unnecessary line breaks that hurt readability
  3. Constant definitions with tuples - Breaking logical units apart

History of Complaints and Prior Work

  1. Recent example in JumpProcesses.jl PR CompatHelper: bump compat for AbstractAlgebra to 0.26, (keep existing compat) #504: Update code formatting with JuliaFormatter JumpProcesses.jl#504

    • Multiple instances of awkward formatting
    • Reviewer comment: "JuliaFormatter is still as broken as it was over a year ago"
  2. Specific examples from that PR:

    # Before (readable):
    discrete_modified, saved_in_cb = DiffEqBase.apply_discrete_callback\!(integrator, integrator.opts.callback.discrete_callbacks...)
    
    # After JuliaFormatter (awkward):
    discrete_modified,
    saved_in_cb = DiffEqBase.apply_discrete_callback\!(integrator, integrator.opts.callback.discrete_callbacks...)
  3. Similar issues in Catalyst.jl:

    # JuliaFormatter was breaking:
    fig, ax, plt = graphplot(plot_graph; node_color = vals[1], edge_width = 2, layout = Spring())
    
    # Into:
    fig, ax,
    plt = graphplot(plot_graph; node_color = vals[1], edge_width = 2, layout = Spring())

Related JuliaFormatter Issues and PRs

This is a well-known problem with many attempts to fix it:

Issues:

Pull Requests attempting fixes:

Despite all these attempts, the issue persists, particularly affecting SciML packages where plotting functions and scientific computing patterns often involve multiple return values.

The Solution

I'm working on a comprehensive PR to JuliaFormatter that makes the formatter more conservative about breaking lines, building on the lessons learned from previous attempts. The key improvements:

  1. Proportional margin thresholds - Only break if significantly over margin (10% for calls, 15% for binary ops)
  2. Special handling for tuple assignments - Keep LHS tuples together when reasonable
  3. Smarter nesting decisions - Consider the semantic meaning of code structures
  4. SciMLStyle-specific improvements - Better handling of common SciML patterns

Verification

All changes in this PR have been tested with the updated JuliaFormatter to ensure they won't be reverted. The test suite includes specific cases for:

  • Plotting function assignments (fig, ax, plt = ...)
  • Constant tuple definitions
  • Function calls with multiple return values
  • Binary operations with long expressions

This should significantly improve code readability across the SciML ecosystem once the JuliaFormatter improvements are merged.

Why This Matters

The current formatting makes code harder to read and understand, particularly for:

  • Scientists and researchers using plotting functions
  • Code that follows Julia's multiple return value conventions
  • Mathematical expressions that shouldn't be arbitrarily split

By fixing these formatting issues, we improve the developer experience across the entire SciML ecosystem.

@ChrisRackauckas
Copy link
Member Author

Detailed breakdown of changes in this PR

Here's a file-by-file breakdown of the formatting fixes:

1. ext/CatalystCairoMakieExtension/cairo_makie_extension_spatial_modelling.jl

  • Lines 27-31: Fixed fig, ax, plt = scatterlines(...)
  • Lines 103-108: Fixed fig, ax, hm = heatmap(...)

2. ext/CatalystGraphMakieExtension/graph_makie_extension_spatial_modelling.jl

  • Lines 30-31: Fixed fig, ax, plt = graphplot(...)

3. src/chemistry_functionality.jl

  • Line 278: Kept binary operation with warning on single line for readability

4. src/dsl.jl

  • Line 6: Fixed bwd_arrows constant definition
  • Lines 11-13: Fixed option_keys tuple to be more readable
  • Lines 301-303: Fixed vs_inferred, diffs_inferred, equations = ...
  • Lines 307-309: Fixed obsexpr, obs_eqs, obs_syms = ...

5. src/reaction.jl

  • Line 705: Fixed NON_CONSTANT_JUMP_SCALES constant definition

6. src/reactionsystem_serialisation/serialise_fields.jl

  • Lines 105-110: Fixed three dependency_split\! calls
  • Line 433: Fixed CONTINUOUS_EVENTS_FS constant
  • Line 489: Fixed DISCRETE_EVENTS_FS constant
  • Line 561: Fixed CONNECTION_TYPE_FS constant

Summary of patterns fixed:

  1. Tuple assignments: All fig, ax, plt = patterns now stay on one line
  2. Constant definitions: Tuple constants stay together when reasonable
  3. Multiple assignments: LHS variables stay together (e.g., a, b, c = ...)
  4. Function calls: Arguments stay with the function name when possible
  5. Binary operations: Long warning messages stay readable

These changes make the code significantly more readable while maintaining the same functionality.

@ChrisRackauckas
Copy link
Member Author

How to Identify and Fix JuliaFormatter Line Split Issues

What to Look For

When reviewing code after running JuliaFormatter, watch for these problematic patterns:

1. Split Tuple Assignments

Bad:

fig, ax,
plt = scatterlines(data)

x, y,
z = process_coordinates(data)

Good:

fig, ax, plt = scatterlines(data)

x, y, z = process_coordinates(data)

2. Split Function Calls

Bad:

result = some_function(
    arg1)

writable_ps = dependency_split\!(remaining_ps,
    [remaining_ps; remaining_sps; remaining_vars])

Good:

result = some_function(arg1)

writable_ps = dependency_split\!(remaining_ps, [remaining_ps; remaining_sps; remaining_vars])

3. Split Constant Definitions

Bad:

const MY_TUPLE = (first_element, second_element,
    third_element)

CONNECTION_TYPE_FS = (
    func1, func2, func3)

Good:

const MY_TUPLE = (first_element, second_element, third_element)

CONNECTION_TYPE_FS = (func1, func2, func3)

4. Split Binary Operations

Bad:

condition &&
    (@warn "Long warning message")

Good:

condition && (@warn "Long warning message")

How to Fix These Issues

  1. Manual Reversion

    • After running JuliaFormatter, review the diff carefully
    • Look for the patterns above
    • Manually revert lines that were split awkwardly
    • Keep logical units together (especially LHS of assignments)
  2. Using Git to Help

    # After formatting, review changes:
    git diff
    
    # Selectively revert problematic hunks:
    git checkout -p
    # Then choose 'n' for good changes, 'y' to revert bad splits
  3. Quick Search Patterns

    # Find potential split assignments:
    grep -n "^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]* =" . -r --include="*.jl"
    
    # Find lines ending with comma (potential split tuples):
    grep -n ",$" . -r --include="*.jl"
    
    # Find function calls split from their names:
    grep -B1 "^[[:space:]]*(" . -r --include="*.jl"

Rules of Thumb

  1. Keep semantic units together: If variables are logically related (like fig, ax, plt), they should stay on the same line
  2. Prefer readability over strict line limits: A 95-character line that's readable is better than a split at 92 characters
  3. Don't split short expressions: If the entire expression is < 120 characters, it probably shouldn't be split
  4. Maintain symmetry: If you have multiple similar lines, format them consistently

Common Patterns in SciML

These patterns should generally stay together:

  • fig, ax, plt = plotting_function(...)
  • u, v, w = solve(...)
  • dx, dy, dz = gradient(...)
  • discrete_modified, saved_in_cb = callback_function(...)
  • Constant tuples defining interfaces or function sets

When Splits ARE Appropriate

Line breaks are good when:

  • Function arguments are semantically different and numerous
  • Each argument is complex (not just a simple variable)
  • The line is genuinely too long (>120 chars) with complex expressions
  • Breaking improves clarity by grouping related arguments

Remember: The goal is readable, maintainable code. JuliaFormatter is a tool to help, not a strict rulebook. Use your judgment to keep code clear and logical.

@ChrisRackauckas
Copy link
Member Author

domluna/JuliaFormatter.jl#934 should be the final PR required in order to ensure that behavior doesn't come back. All of these cases were added as MWEs to the test suite.

@ChrisRackauckas
Copy link
Member Author

Major note to Claude: JuliaFormatter will not reverse line breaks. The best way to solve such issues is to fully revert by bring all line breaks back to a single line and then letting the formatter run

@ChrisRackauckas
Copy link
Member Author

I'll handle Arblib updates separately.

@ChrisRackauckas ChrisRackauckas merged commit 2940531 into SciML:master Aug 2, 2025
15 of 17 checks passed
@ChrisRackauckas ChrisRackauckas deleted the fix-formatting-issues branch August 2, 2025 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant