Skip to content

Fix type mismatch error in reflowMultilineStringLiterals #979

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

Conversation

dongdong867
Copy link
Contributor

Problem description

  • Environment: swift-format with v601.0.0 or build-in support with Xcode 16.3

  • Issue:
    When running swift-format <path> command with a custom configuration that contains reflowMultilineStringLiterals will receive the below error

    <unknown>: error: Unable to read configuration for <path-to-file>: The data couldn’t be read because it isn’t in the correct format.
    
  • How to reproduce:

    1. Create a .swift-format config file in any project with swift-format setup
    2. Make sure the config file contains the setting of reflowMultilineStringLiterals based on the document.
    3. Run the swift-format <path> command and the error should show in the console

Root cause

The MultilineStringReflowBehavior enum in /Sources/SwiftFormat/API/Configuration.swift is used to parse the value of reflowMultilineStringLiterals from the config file, however, the origin enum raw type is missing which causes the decodeIfPresent(_:forKey:) function throws an DecodingError.typeMismatch error when decoding.

The root cause can be verified by running the swift-format dump-configuration command. The console shows that the default value of key reflowMultilineStringLiterals is structured as:

"reflowMultilineStringLiterals" : {
  "never" : {

  }
},

but according to the documentation, the value of ⁠reflowMultilineStringLiterals should be a string like:

"reflowMultilineStringLiterals": "never",

Solution

By adding a raw type String to the enum MultilineStringReflowBehavior:

// Before
enum MultilineStringReflowBehavior: Codable { ... }

// After
enum MultilineStringReflowBehavior: String, Codable { ... }

This solves the current issue and makes the config file align with what is defined in the documentation.

{
  "fileScopedDeclarationPrivacy" : {
    "accessLevel" : "private"
  },
  "indentConditionalCompilationBlocks" : true,
  "indentSwitchCaseLabels" : false,
  "indentation" : {
    "spaces" : 2
  },
  "lineBreakAroundMultilineExpressionChainComponents" : false,
  "lineBreakBeforeControlFlowKeywords" : false,
  "lineBreakBeforeEachArgument" : false,
  "lineBreakBeforeEachGenericRequirement" : false,
  "lineBreakBetweenDeclarationAttributes" : false,
  "lineLength" : 100,
  "maximumBlankLines" : 1,
  "multiElementCollectionTrailingCommas" : true,
  "noAssignmentInExpressions" : {
    "allowedFunctions" : [
      "XCTAssertNoThrow"
    ]
  },
  "prioritizeKeepingFunctionOutputTogether" : false,

  "reflowMultilineStringLiterals" : "never",

  "respectsExistingLineBreaks" : true,
  "rules" : { "...ignored with unchanged" }
  "spacesAroundRangeFormationOperators" : false,
  "spacesBeforeEndOfLineComments" : 2,
  "tabWidth" : 8,
  "version" : 1
}

@allevato
Copy link
Member

allevato commented Apr 7, 2025

Unfortunately, the current behavior is part of the swift-format release in the 6.1 toolchain (Xcode 16.3), so we can't just add a raw value to this enum without breaking how configurations created for that version of the tool would decode.

What I think we can do is:

  1. Add the String raw value as you've done here, so that future versions use the preferred format
  2. Add a custom implementation of init(from: Decoder) that detects either format: a simple string or an object with a single key.

I think the easiest way to implement (2) would be create a second private copy of the enum without the String raw value, called something like LegacyMultilineStringReflowBehavior. Then, you try to decode it as a String first, and if that fails, try to decode it as the legacy enum. If the first succeeds, convert it with init(rawValue:). If the second succeeds, map it from the legacy enum to the proper enum. If both fail, throw the error.

That would avoid us having to bump the configuration version just for this oversight.

@dongdong867
Copy link
Contributor Author

@allevato Thanks for your soundness consideration on this issue.

I've implement the legacy private enum based on your comment, and I've also added some document to the reflowMultilineStringLiterals section. Please felt free to leave some comment if there's any thing I need to change.

Copy link
Member

@allevato allevato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a couple small tests to Tests/SwiftFormatTests/API/ConfigurationTests.swift that verify that both formats decode correctly, just to make sure we don't accidentally regress this in the future?

Comment on lines 268 to 274
var isNever: Bool {
self == .never
}

var isAlways: Bool {
self == .always
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need these since nothing else in the formatter will access them.


> [!NOTE]
> This setting should be specified as a string value (e.g. "never")
> For backward compatibility with swift-format version 601.0.0, the configuration also accepts the legacy object format where the setting is specified as an object with a single key (e.g., ⁠{ "never": {} }).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> For backward compatibility with swift-format version 601.0.0, the configuration also accepts the legacy object format where the setting is specified as an object with a single key (e.g., ⁠{ "never": {} }).
> For backward compatibility with swift-format version 601.0.0, the configuration also accepts the legacy object format where the setting is specified as an object with a single key (e.g., `⁠{ "never": {} }`).

@dongdong867
Copy link
Contributor Author

@allevato comments addressed, please check it again, thx!

@allevato
Copy link
Member

allevato commented Apr 7, 2025

Can you please add a couple small tests to Tests/SwiftFormatTests/API/ConfigurationTests.swift that verify that both formats decode correctly, just to make sure we don't accidentally regress this in the future?

I think you missed this one 🙂

@dongdong867
Copy link
Contributor Author

@allevato Sorry for missing the comment, tests are added, please check.

Copy link
Member

@allevato allevato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! If you accept the suggestions here, I can kick off the tests.

Comment on lines 69 to 75
typealias MultilineStringReflowBehavior = Configuration.MultilineStringReflowBehavior

let testCases = [
"never": MultilineStringReflowBehavior.never,
"always": MultilineStringReflowBehavior.always,
"onlyLinesOverLength": MultilineStringReflowBehavior.onlyLinesOverLength,
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
typealias MultilineStringReflowBehavior = Configuration.MultilineStringReflowBehavior
let testCases = [
"never": MultilineStringReflowBehavior.never,
"always": MultilineStringReflowBehavior.always,
"onlyLinesOverLength": MultilineStringReflowBehavior.onlyLinesOverLength,
]
let testCases: [String: Configuration.MultilineStringReflowBehavior] = [
"never": .never,
"always": .always,
"onlyLinesOverLength": .onlyLinesOverLength,
]

Comment on lines 90 to 96
typealias MultilineStringReflowBehavior = Configuration.MultilineStringReflowBehavior

let testCases = [
"{ \"never\": {} }": MultilineStringReflowBehavior.never,
"{ \"always\": {} }": MultilineStringReflowBehavior.always,
"{ \"onlyLinesOverLength\": {} }": MultilineStringReflowBehavior.onlyLinesOverLength,
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
typealias MultilineStringReflowBehavior = Configuration.MultilineStringReflowBehavior
let testCases = [
"{ \"never\": {} }": MultilineStringReflowBehavior.never,
"{ \"always\": {} }": MultilineStringReflowBehavior.always,
"{ \"onlyLinesOverLength\": {} }": MultilineStringReflowBehavior.onlyLinesOverLength,
]
let testCases: [String: Configuration.MultilineStringReflowBehavior] = [
"{ \"never\": {} }": .never,
"{ \"always\": {} }": .always,
"{ \"onlyLinesOverLength\": {} }": .onlyLinesOverLength,
]

@dongdong867
Copy link
Contributor Author

@allevato Addressed, thanks for your review!

@allevato
Copy link
Member

allevato commented Apr 7, 2025

This looks like a bug in API digester:

4 breaking changes detected in SwiftFormat:
  💔 API breakage: func Configuration.MultilineStringReflowBehavior.hash(into:) has been removed
  💔 API breakage: func Configuration.MultilineStringReflowBehavior.encode(to:) has been removed
  💔 API breakage: var Configuration.MultilineStringReflowBehavior.hashValue has been removed
  💔 API breakage: constructor Configuration.MultilineStringReflowBehavior.init(from:) has been removed

Adding a raw value to the enum certainly shouldn't have removed any of those declarations; the type is still Hashable and Codable.

@ahoppen What do you think? I'm inclined to say that @dongdong867 should just copy these into api-breakages.txt but I wanted to call this out first in case I'm missing something.

@ahoppen
Copy link
Member

ahoppen commented Apr 8, 2025

Let’s add them to api-breakages.txt and reconsider the value of the API breakage check separately.

@dongdong867
Copy link
Contributor Author

@allevato @ahoppen Value added to api-breakages.txt, please review, thanks.

Copy link
Member

@allevato allevato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing this!

@allevato allevato merged commit 661aa9e into swiftlang:main Apr 8, 2025
20 checks passed
@dongdong867 dongdong867 deleted the fix_enum_raw_type_mismatch_in_configuration branch April 8, 2025 15:34
ahoppen pushed a commit to ahoppen/swift-format that referenced this pull request Apr 10, 2025
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.

3 participants