August 22, 2017·4 min read·engineeringswifttoolingcompilers

Swift opens the compiler logic behind local refactoring

Swift's local refactoring engine combines semantic selection analysis with textual edits. Its open implementation separates deciding whether an action applies from performing the transformation.

Extracting an expression is more demanding than moving a substring into a variable declaration. The selected text must represent an expression, its type must be usable, and the insertion must make sense in the surrounding code. Swift's local refactoring engine gives transformation authors access to compiler analysis before asking them to produce source edits.

The August 22 introduction explains the engine included in Xcode 9. The logic for local transformations lives entirely in the compiler and SourceKit and is now open source. Contributors can implement a new action in the Swift repository, test it with a command-line utility, and use a locally built toolchain to expose it in Xcode. This creates a contribution route for editor behavior that depends on language semantics.

I find Swift consistently well designed, but building small applications in it already takes more time than I like. Tooling that uses the compiler's understanding to perform a routine source change is useful for a separate reason. It reduces the amount of manual editing needed to preserve a program's structure while reorganizing it.

The open boundary is local to one file

A local refactoring changes code within one source file. The announcement gives Extract Method and Extract Repeated Expression as examples. A global operation, such as renaming a method across files or languages, requires additional coordination by Xcode. That global coordination cannot currently be implemented solely in the open Swift codebase.

This boundary matters when interpreting the announcement. Opening local transformation logic does not open every part of project-wide refactoring. A compiler can understand a selected file while the editor still has to coordinate changes across a project. The implementation opportunity described here is substantial, but its scope is explicitly local.

Within that scope, actions have two selection forms. A cursor-based action gets enough information from one source position. A range-based action requires a start and end. Rename is an example of the former, while extracting a method needs a selected range. These categories determine which pre-analyzed information an action receives.

ResolvedCursorInfo can identify the expression at a position or the declaration corresponding to a name. ResolvedRangeInfo supplies information about a source interval, including whether it has multiple entry or exit points. A refactoring author therefore begins with a semantic description of the selection instead of building all that analysis from raw line and column positions.

Applicability and transformation are separate operations

When a user selects a location, Xcode asks sourcekitd which actions apply. Each implemented action examines the resolved selection and reports its applicability. Xcode displays the resulting list. Choosing an action then causes another request that verifies applicability and asks the action to produce textual source edits.

This split gives an action two responsibilities. It must avoid advertising itself where the transformation is inappropriate, and it must generate the right change when invoked. Testing only the final output would leave incorrect menu entries unexamined. Testing only the menu would leave the transformation itself unverified.

The refactoring-kind declarations linked by the announcement record the selection category and communication identifier. The string-localization action has this entry.

CURSOR_REFACTORING(LocalizeString, "Localize String", localize.string)

The first value supplies an internal name, the string is the displayed action name, and the final value is the stable identifier used between the toolchain and source editor. The declaration also supports generating the corresponding class and callers. A contributor can concentrate on applicability and edit generation within that arrangement. Other entries declare local rename and extraction operations using the corresponding categories.

Compiler analysis constrains textual changes

The introduction's localization example checks that the cursor points to a string literal without interpolation. Its transformation inserts the surrounding NSLocalizedString call and a comment argument. This demonstrates a small edit whose validity still depends on understanding the selected expression.

The article's C++ snippets are explanatory fragments, and one explicitly marks an illustrative interpolation check as not being a real API. They should not be copied wholesale as a compilable implementation. The useful design point is that the action checks the kind of expression before it asks the edit consumer to insert text around it.

Extract Expression uses range information instead. Its shown applicability checks require a single expression and reject an absent or erroneous type. The article omits further conditions, so those visible checks are not a complete specification of valid extraction. The change then constructs a local declaration initialized with the selected expression and replaces the original occurrence with the variable name.

That division preserves an important relationship between semantics and editing. The compiler analysis establishes what the selection represents, while the edit consumer expresses modifications to the source text. An editor integration can apply those edits without independently recreating the language-specific applicability rules for every transformation.

Tests cover both the offered action and its output

The swift-refactor utility is built alongside the compiler and exercises both responsibilities. Contextual tests provide a source position and inspect the available action names. Range-based tests add an end position. The test runner substitutes the tool's path for %refactor, and FileCheck verifies expected output.

Negative applicability cases matter here. The article specifically mentions checking that string localization is not offered for an interpolated literal. A positive test establishes that an action appears in one valid context. A negative test establishes part of the boundary that keeps the menu from offering an inappropriate transformation.

Transformation tests start with source before the refactoring, perform the chosen action and compare the generated source with an expected file. A diagnostic can also explain why an operation aborts. The rename example rejects an invalid identifier using the compiler's diagnostic machinery. These tests and diagnostics give contributors a concrete way to specify behavior beyond the action's menu label.

I value the reusable semantic checks because they make an editor action easier to reason about than a general text substitution. The open implementation still needs tests for its particular assumptions. Contributors can build a toolchain, install it under the developer toolchains directory and select it in Xcode to inspect the same action through the editor integration.