Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion Sources/ArgumentParser/Parsing/SplitArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,14 @@ func parseIndividualArg(_ arg: String, at position: Int) throws
case 0:
return [.value(arg, index: index)]
case 1:
// Long option:
// If the remainder is a numeric value (e.g. "-5", "-3.14"), treat it as a value
// to allow passing negative numbers to positional arguments.
// This preserves typical short/long option parsing while enabling
// negative numeric literals to be consumed as values.
if remainder.allSatisfy({ $0.isNumber }) || isDecimalNumber(remainder) {
return [.value(arg, index: index)]
}
// Otherwise, treat as an option (short or long-with-single-dash)
let parsed = try ParsedArgument(longArgWithSingleDashRemainder: remainder)

// Short options:
Expand Down Expand Up @@ -636,6 +643,22 @@ func parseIndividualArg(_ arg: String, at position: Int) throws
}
}

/// Detects a simple decimal number like "123" or "3.14" (no sign).
private func isDecimalNumber(_ s: Substring) -> Bool {
// must contain exactly one dot or none; all other chars are digits
var dotCount = 0
for ch in s {
if ch == "." {
dotCount += 1
if dotCount > 1 { return false }
} else if !ch.isNumber {
return false
}
}
// not just a dot
return !s.isEmpty && !(s.count == 1 && s.first == ".")
}

extension SplitArguments {
/// Parses the given input into an array of `Element`.
///
Expand Down
34 changes: 34 additions & 0 deletions Tests/ArgumentParserUnitTests/NegativeNumberArgumentTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Argument Parser open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import XCTest
import ArgumentParser

final class NegativeNumberArgumentTests: XCTestCase {
struct Absolute: ParsableCommand {
@Argument var number: Int
}

func testParsesNegativeIntegerAsArgument() throws {
let cmd = try Absolute.parse(["-5"]) // should be treated as value, not option
XCTAssertEqual(cmd.number, -5)
}

struct FloatArg: ParsableCommand {
@Argument var value: Double
}

func testParsesNegativeDoubleAsArgument() throws {
let cmd = try FloatArg.parse(["-3.14"]) // negative decimal
XCTAssertEqual(cmd.value, -3.14, accuracy: 1e-9)
}
}