From 48c267f47e23387a0f5a33cd8814f0ba6bba9a6d Mon Sep 17 00:00:00 2001 From: ensan-hcl Date: Tue, 15 Jul 2025 14:05:44 -0700 Subject: [PATCH] fix: itta-then-delete crash --- .../Core/SuffixReplacementProcessing.swift | 8 ++++--- .../DictionaryManagement/DicdataStore.swift | 4 ++-- .../ConverterTests/ConverterTests.swift | 24 +++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Core/SuffixReplacementProcessing.swift b/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Core/SuffixReplacementProcessing.swift index 24e29ed..e62078c 100644 --- a/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Core/SuffixReplacementProcessing.swift +++ b/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Core/SuffixReplacementProcessing.swift @@ -58,12 +58,12 @@ extension Kana2Kanji { } else { // (2) let rawNodes = latticeIndices.map { index in - let inputRange: (startIndex: Int, endIndexRange: Range?)? = if let iIndex = index.inputIndex { + let inputRange: (startIndex: Int, endIndexRange: Range?)? = if let iIndex = index.inputIndex, max(commonInputCount, iIndex) < inputCount { (iIndex, max(commonInputCount, iIndex) ..< inputCount) } else { nil } - let surfaceRange: (startIndex: Int, endIndexRange: Range?)? = if let sIndex = index.surfaceIndex { + let surfaceRange: (startIndex: Int, endIndexRange: Range?)? = if let sIndex = index.surfaceIndex, max(commonSurfaceCount, sIndex) < surfaceCount { (sIndex, max(commonSurfaceCount, sIndex) ..< surfaceCount) } else { nil @@ -92,7 +92,9 @@ extension Kana2Kanji { } // 変換した文字数 let nextIndex = indexMap.dualIndex(for: node.range.endIndex) - self.updateNextNodes(with: node, nextNodes: addedNodes[index: nextIndex], nBest: N_best) + if nextIndex != .bothIndex(inputIndex: inputCount, surfaceIndex: surfaceCount) { + self.updateNextNodes(with: node, nextNodes: addedNodes[index: nextIndex], nBest: N_best) + } } } lattice.merge(addedNodes) diff --git a/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift b/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift index e2b74f4..f72db8c 100644 --- a/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift +++ b/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift @@ -488,7 +488,7 @@ public final class DicdataStore { inputRange.startIndex + self.maxlength ) if inputRange.startIndex > toInputIndexLeft || toInputIndexLeft >= toInputIndexRight { - debug(#function, "index is wrong") + debug(#function, "index is wrong", inputRange) return [] } inputProcessRange = .init(leftIndex: inputRange.startIndex, rightIndexRange: toInputIndexLeft ..< toInputIndexRight) @@ -504,7 +504,7 @@ public final class DicdataStore { surfaceRange.startIndex + self.maxlength ) if surfaceRange.startIndex > toSurfaceIndexLeft || toSurfaceIndexLeft >= toSurfaceIndexRight { - debug(#function, "index is wrong") + debug(#function, "index is wrong", surfaceRange) return [] } surfaceProcessRange = .init(leftIndex: surfaceRange.startIndex, rightIndexRange: toSurfaceIndexLeft ..< toSurfaceIndexRight) diff --git a/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift b/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift index 011ed86..fab5abe 100644 --- a/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift +++ b/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift @@ -132,6 +132,30 @@ final class ConverterTests: XCTestCase { } } + // memo: このケースでfatalErrorが発生する不具合が生じることがあった + func testIttaAndThenDelete() async throws { + let converter = await KanaKanjiConverter() + var c = ComposingText() + let text = "itta" + // 許容される変換結果 + let possibles = [ + "いった", + "行った", + "言った" + ] + for char in text { + c.insertAtCursorPosition(String(char), inputStyle: .roman2kana) + let results = await converter.requestCandidates(c, options: requestOptions()) + if c.input.count == text.count { + XCTAssertTrue(possibles.contains(results.mainResults.first!.text)) + } + } + // 1文字削除 + c.deleteBackwardFromCursorPosition(count: 1) + let results = await converter.requestCandidates(c, options: requestOptions()) + XCTAssertTrue(results.mainResults.contains { $0.text == "言っ" }) + } + // 1文字ずつ入力するが、時折削除を行う // memo: 内部実装としてはdeleted_last_nのテストを意図している func testGradualConversionWithDelete() async throws {