Skip to content
Merged
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
15 changes: 11 additions & 4 deletions Sources/Compiler/Parse/Lexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,20 @@ struct Lexer {
currentColumn += 1
}
}

/// Gets `currentIndex` through a noninlined call. Used to work around
/// an optimizer bug using a stale value of `currentIndex`.
@inline(never)
private func currentIndexOptimizerWorkaround() -> String.Index {
return currentIndex
}

// SQLite does not seem to really care what goes between the escape delimiters.
// Table names will gladly take newlines and such.
private mutating func parseEscapedIdentifier(closing: Character) -> Token {
let tokenStart = startLocation()
advance() // Opening
let identifierStart = currentIndex
let identifierStart = currentIndexOptimizerWorkaround()

while let current, current != closing {
advance()
Expand Down Expand Up @@ -323,7 +330,7 @@ struct Lexer {
advance() // 0
advance() // x or X

let numberStart = currentIndex
let numberStart = currentIndexOptimizerWorkaround()

while let current, Lexer.hexDigits.contains(current) || current == "_" {
advance()
Expand All @@ -345,8 +352,8 @@ struct Lexer {
private mutating func parseStringContents() -> (Substring, SourceLocation) {
let tokenStart = startLocation()
advance()
let stringStart = currentIndex
let stringStart = currentIndexOptimizerWorkaround()

while let current, current != "'" {
advance()
}
Expand Down
81 changes: 81 additions & 0 deletions Tests/CompilerTests/LexerSliceReleaseTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// LexerSliceReleaseTests.swift
//
//
// Created by Wes Wickwire on 6/27/26.
//

import Testing
@testable import Compiler

/// These tests only failed in a release build.
@Suite
struct LexerSliceReleaseTests {
@Test func escapedIdentifierStripsDelimiters() {
#expect(firstIdentifier("\"MyModel.ID\"") == "MyModel.ID")
#expect(firstIdentifier("[MyModel.ID]") == "MyModel.ID")
#expect(firstIdentifier("`MyModel.ID`") == "MyModel.ID")
}

@Test func stringLiteralStripsQuotes() {
#expect(firstString("'hello world'") == "hello world")
}

@Test func hexLiteral() {
#expect(firstHex("0xFF") == 255)
}

@Test func scientificNotation() {
#expect(firstDouble("1e3") == 1000)
#expect(firstDouble("1e-2") == 0.01)
}

@Test func aliasedColumnTypeGeneratesUnquotedName() {
var compiler = Compiler()
let (_, diags) = compiler.compile(migration: """
CREATE TABLE myTable (
id INTEGER AS "MyModel.ID"
);
""")
#expect(diags.isEmpty)
let table = compiler.schema.tables.values.first!
let col = table.columns.values.first!
#expect(!String(describing: col.type).contains("\""))
}

private func firstIdentifier(_ src: String) -> String? {
var lexer = Lexer(source: src)
while true {
let t = lexer.next()
if case .eof = t.kind { return nil }
if case let .identifier(v) = t.kind { return String(v) }
}
}

private func firstString(_ src: String) -> String? {
var lexer = Lexer(source: src)
while true {
let t = lexer.next()
if case .eof = t.kind { return nil }
if case let .string(v) = t.kind { return String(v) }
}
}

private func firstHex(_ src: String) -> Int? {
var lexer = Lexer(source: src)
while true {
let t = lexer.next()
if case .eof = t.kind { return nil }
if case let .hex(v) = t.kind { return v }
}
}

private func firstDouble(_ src: String) -> Double? {
var lexer = Lexer(source: src)
while true {
let t = lexer.next()
if case .eof = t.kind { return nil }
if case let .double(v) = t.kind { return v }
}
}
}
Loading