Skip to content

Commit e1842b2

Browse files
committed
Type hints
1 parent 3aa22c9 commit e1842b2

7 files changed

Lines changed: 147 additions & 90 deletions

File tree

Sources/Compiler/Gen/SwiftLanguage.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ public struct SwiftLanguage: Language {
5252
case let .optional(ty): "\(builtinType(for: ty))?"
5353
case let .row(.unknown(ty)): "[\(builtinType(for: ty))]"
5454
case .var, .fn, .row, .error: "Any"
55-
case .alias(_, let alias): alias.description
55+
case .alias(_, let alias):
56+
switch alias {
57+
case .explicit(let type):
58+
type.description
59+
case .hint(let hint):
60+
switch hint {
61+
case .bool: "Bool"
62+
}
63+
}
5664
}
5765
}
5866

Sources/Compiler/Sema/Builtins.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ enum Builtins {
1212
static let negate = Function(.var(0), returning: .var(0))
1313
static let bitwiseNot = Function(.var(0), returning: .var(0))
1414
static let pos = Function(.var(0), returning: .var(0))
15-
static let between = Function(.var(0), .var(0), .var(0), returning: .integer)
15+
static let between = Function(.var(0), .var(0), .var(0), returning: .boolean)
1616
static let arithmetic = Function(.var(0), .var(0), returning: .var(0))
1717
static let divide = Function(.var(0), .var(0), returning: .var(0)) { types, exprs, location, diagnostics in
1818
func isInt(_ type: Type, expr: any ExprSyntax) -> Bool {
@@ -34,8 +34,8 @@ enum Builtins {
3434
at: location
3535
))
3636
}
37-
static let comparison = Function(.var(0), .var(0), returning: .integer)
38-
static let `in` = Function(.var(0), .row(.unknown(.var(0))), returning: .integer)
37+
static let comparison = Function(.var(0), .var(0), returning: .boolean)
38+
static let `in` = Function(.var(0), .row(.unknown(.var(0))), returning: .boolean)
3939
static let concatOp = Function(.var(0), .var(1), returning: .text)
4040
static let extract = Function(.var(0), returning: .var(1))
4141
static let extractJson = Function(.var(0), returning: .any)
@@ -44,7 +44,7 @@ enum Builtins {
4444
static let match = Function(.var(0), .text, returning: .integer)
4545
static let regexp = Function(.text, .text, returning: .integer)
4646
static let glob = Function(.text, .text, returning: .integer)
47-
static let isNullPostfix = Function(.var(0), returning: .integer)
47+
static let isNullPostfix = Function(.var(0), returning: .boolean)
4848

4949
static let functions: OrderedDictionary<Substring, Function> = {
5050
// TODO: Clean this up. SQLite isnt casing dependant but we are at the moment.

Sources/Compiler/Sema/InferenceState.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ struct InferenceState {
117117
return .var(TypeVariable(tyVarCounter, kind: kind))
118118
}
119119

120+
/// Gives the type a hint
121+
mutating func hint(
122+
type hint: Type.Alias.Hint,
123+
for type: Type,
124+
at location: SourceLocation
125+
) {
126+
unify(type, with: .alias(type, .hint(hint)), at: location)
127+
}
128+
120129
/// Gets the final type from the solution for the type if its a ty var.
121130
/// If `defaultIfTyVar` is true, the type will be given a
122131
/// default value if it is a type var.

Sources/Compiler/Sema/StmtTypeChecker.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,7 @@ extension StmtTypeChecker {
10271027
}
10281028
}
10291029

1030-
private func assumeRow(_ ty: Type) -> Type.Row {
1030+
private func assumeRow(_ ty: Type) -> Row {
10311031
guard case let .row(rowTy) = ty else {
10321032
assertionFailure("This cannot happen")
10331033
return .fixed([])
@@ -1247,7 +1247,7 @@ extension StmtTypeChecker {
12471247
let nominal: Type = .nominal(column.type.name.value)
12481248

12491249
let type: Type = if let alias = column.type.alias {
1250-
.alias(nominal, alias.identifier.value)
1250+
.alias(nominal, .explicit(alias.identifier.value))
12511251
} else {
12521252
nominal
12531253
}

Sources/Compiler/Sema/Type.swift

Lines changed: 94 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public enum Type: Equatable, CustomStringConvertible, Sendable {
2424
/// A type that has been aliased. These are not in SQL by default
2525
/// but are from the layer on top that we are adding so a user
2626
/// can replace a `INTEGER` with a `Bool`
27-
indirect case alias(Type, Substring)
27+
indirect case alias(Type, Alias)
2828
/// There was an error somewhere in the analysis. We can just return
2929
/// an `error` type and continue the analysis. So if the user makes up
3030
/// 3 columns, they can get all 3 errors at once.
@@ -37,79 +37,48 @@ public enum Type: Equatable, CustomStringConvertible, Sendable {
3737
/// `error + INTEGER = INTEGER` - Good
3838
case error
3939

40-
/// Rows or "Tables" can come in the form of two ways.
41-
/// Named and unnamed. Named is obviously a table from
42-
/// a FROM or JOIN. Unnamed would be from a subquery or
43-
/// a row expression `(?, ?, ?)`.
44-
public enum Row: Equatable, Sendable, ExpressibleByArrayLiteral {
45-
/// A row of a defined set of types.
46-
case fixed([Type])
47-
/// This is a special row that we don't know the inner types of.
48-
/// It assumes that all types in it are the same type and of an
49-
/// unbounded length. Allows us to define functions like `IN` that
50-
/// take a row as an input but we are unsure of what the inner values are.
51-
indirect case unknown(Type)
52-
53-
public static let empty: Row = .fixed([])
54-
55-
public init(arrayLiteral elements: Type...) {
56-
self = .fixed(elements)
57-
}
58-
59-
var first: Type? {
60-
return switch self {
61-
case let .fixed(v): v.first
62-
case let .unknown(t): t
63-
}
64-
}
65-
66-
var count: Int {
67-
return switch self {
68-
case let .fixed(v): v.count
69-
case .unknown: 1
70-
}
71-
}
72-
73-
var types: [Type] {
74-
return switch self {
75-
case let .fixed(v): v
76-
case let .unknown(t): [t]
77-
}
78-
}
79-
80-
var isUnknown: Bool {
81-
guard case .unknown = self else { return false }
82-
return true
83-
}
84-
85-
func apply(_ s: Substitution) -> Row {
86-
return switch self {
87-
case let .fixed(v): .fixed(v.map { $0.apply(s) })
88-
case let .unknown(t): .unknown(t.apply(s))
89-
}
90-
}
91-
92-
func mapTypes(_ transform: (Type) -> Type) -> Row {
93-
switch self {
94-
case let .fixed(values):
95-
return .fixed(values.map(transform))
96-
case let .unknown(value):
97-
return .unknown(transform(value))
98-
}
99-
}
100-
}
101-
10240
static let text: Type = .nominal("TEXT")
10341
static let int: Type = .nominal("INT")
10442
static let integer: Type = .nominal("INTEGER")
10543
static let real: Type = .nominal("REAL")
10644
static let blob: Type = .nominal("BLOB")
10745
static let any: Type = .nominal("ANY")
46+
static let boolean: Type = .alias(.integer, .hint(.bool))
10847

10948
static let validTypeNames: Set<Substring> = [
11049
"TEXT", "INT", "INTEGER", "REAL", "BLOB", "ANY"
11150
]
11251

52+
/// An alias for a SQL type. e.g. `INTEGER AS Bool`
53+
public enum Alias: Hashable, Sendable, CustomStringConvertible {
54+
/// Explicitly defined by the user `INTEGER AS Bool`
55+
case explicit(Substring)
56+
/// Used for when the compiler wants to hint at a better type
57+
/// that would be in the final language like a boolean for a
58+
/// equality check, which would normally be an `INTEGER`.
59+
/// We cannot just always use `explicit` since the goal is
60+
/// to lower into many different languages and they might have
61+
/// different names. e.g. `Bool` vs `Boolean` vs `bool`
62+
case hint(Hint)
63+
64+
public enum Hint: Hashable, Sendable, CustomStringConvertible {
65+
case bool
66+
67+
public var description: String {
68+
switch self {
69+
case .bool: "Bool"
70+
}
71+
}
72+
}
73+
74+
public var description: String {
75+
switch self {
76+
case .explicit(let type): type.description
77+
case .hint(let hint): hint.description
78+
}
79+
}
80+
}
81+
11382
public var description: String {
11483
return switch self {
11584
case let .nominal(typeName): typeName.description
@@ -262,3 +231,65 @@ public struct TypeVariable: Hashable, CustomStringConvertible, ExpressibleByInte
262231
return TypeVariable(n, kind: .float)
263232
}
264233
}
234+
235+
/// Rows or "Tables" can come in the form of two ways.
236+
/// Named and unnamed. Named is obviously a table from
237+
/// a FROM or JOIN. Unnamed would be from a subquery or
238+
/// a row expression `(?, ?, ?)`.
239+
public enum Row: Equatable, Sendable, ExpressibleByArrayLiteral {
240+
/// A row of a defined set of types.
241+
case fixed([Type])
242+
/// This is a special row that we don't know the inner types of.
243+
/// It assumes that all types in it are the same type and of an
244+
/// unbounded length. Allows us to define functions like `IN` that
245+
/// take a row as an input but we are unsure of what the inner values are.
246+
indirect case unknown(Type)
247+
248+
public static let empty: Row = .fixed([])
249+
250+
public init(arrayLiteral elements: Type...) {
251+
self = .fixed(elements)
252+
}
253+
254+
var first: Type? {
255+
return switch self {
256+
case let .fixed(v): v.first
257+
case let .unknown(t): t
258+
}
259+
}
260+
261+
var count: Int {
262+
return switch self {
263+
case let .fixed(v): v.count
264+
case .unknown: 1
265+
}
266+
}
267+
268+
var types: [Type] {
269+
return switch self {
270+
case let .fixed(v): v
271+
case let .unknown(t): [t]
272+
}
273+
}
274+
275+
var isUnknown: Bool {
276+
guard case .unknown = self else { return false }
277+
return true
278+
}
279+
280+
func apply(_ s: Substitution) -> Row {
281+
return switch self {
282+
case let .fixed(v): .fixed(v.map { $0.apply(s) })
283+
case let .unknown(t): .unknown(t.apply(s))
284+
}
285+
}
286+
287+
func mapTypes(_ transform: (Type) -> Type) -> Row {
288+
switch self {
289+
case let .fixed(values):
290+
return .fixed(values.map(transform))
291+
case let .unknown(value):
292+
return .unknown(transform(value))
293+
}
294+
}
295+
}

Tests/CompilerTests/Compiler/CompileSimpleSelects.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,12 @@ WHERE value = 1 AND value NOTNULL AND value NOT NULL AND value ISNULL ORDER BY v
248248
SELECT 1 AS value FROM foo
249249
WHERE EXISTS (SELECT * FROM foo)
250250
LIMIT ?;
251+
252+
-- CHECK: SIGNATURE
253+
-- CHECK: OUTPUT_CHUNKS
254+
-- CHECK: CHUNK
255+
-- CHECK: OUTPUT
256+
-- CHECK: isOne (INTEGER AS Bool)
257+
-- CHECK: TABLES
258+
-- CHECK: foo
259+
SELECT id = 1 AS isOne FROM foo;

Tests/CompilerTests/TypeCheckerTests.swift

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,23 @@ class TypeCheckerTests: XCTestCase {
3232
}
3333

3434
func testTypeCheckComparison() {
35-
XCTAssertEqual(.integer, try check("1 + 1 > 1"))
36-
XCTAssertEqual(.integer, try check("1 >= 1 - 1 * 2"))
37-
XCTAssertEqual(.integer, try check("1 > 1"))
38-
XCTAssertEqual(.integer, try check("1 < 1"))
39-
XCTAssertEqual(.integer, try check("1 <= 1"))
40-
XCTAssertEqual(.integer, try check("1 != 1 - 1 * 2"))
41-
XCTAssertEqual(.integer, try check("1 <> 1"))
42-
XCTAssertEqual(.integer, try check("1 = 1"))
43-
XCTAssertEqual(.integer, try check("1 == 1"))
35+
XCTAssertEqual(.boolean, try check("1 + 1 > 1"))
36+
XCTAssertEqual(.boolean, try check("1 >= 1 - 1 * 2"))
37+
XCTAssertEqual(.boolean, try check("1 > 1"))
38+
XCTAssertEqual(.boolean, try check("1 < 1"))
39+
XCTAssertEqual(.boolean, try check("1 <= 1"))
40+
XCTAssertEqual(.boolean, try check("1 != 1 - 1 * 2"))
41+
XCTAssertEqual(.boolean, try check("1 <> 1"))
42+
XCTAssertEqual(.boolean, try check("1 = 1"))
43+
XCTAssertEqual(.boolean, try check("1 == 1"))
4444
}
4545

4646
func testTypeCheckBind() throws {
4747
let result = try result(for: ":foo + 1 > :bar + 2.0 AND :baz")
48-
XCTAssertEqual(.integer, result.type)
48+
XCTAssertEqual(.boolean, result.type)
4949
XCTAssertEqual(.real, type(for: "foo", in: result))
5050
XCTAssertEqual(.real, type(for: "bar", in: result))
51-
XCTAssertEqual(.integer, type(for: "baz", in: result))
51+
XCTAssertEqual(.boolean, type(for: "baz", in: result))
5252
}
5353

5454
func testTypeCheckBind2() throws {
@@ -63,7 +63,7 @@ class TypeCheckerTests: XCTestCase {
6363
""")
6464

6565
let result = try result(for: "bar = ?", in: scope)
66-
XCTAssertEqual(.integer, result.type)
66+
XCTAssertEqual(.boolean, result.type)
6767
XCTAssertEqual(.optional(.integer), type(for: 1, in: result))
6868
XCTAssertEqual("bar", name(for: 1, in: result))
6969
}
@@ -74,7 +74,7 @@ class TypeCheckerTests: XCTestCase {
7474
""")
7575

7676
let result = try result(for: "bar + 1 = ?", in: scope)
77-
XCTAssertEqual(.integer, result.type)
77+
XCTAssertEqual(.boolean, result.type)
7878
XCTAssertEqual(.integer, type(for: 1, in: result))
7979
XCTAssertEqual("bar", name(for: 1, in: result))
8080
}
@@ -85,7 +85,7 @@ class TypeCheckerTests: XCTestCase {
8585
""")
8686

8787
let result = try result(for: "1 + bar = ?", in: scope)
88-
XCTAssertEqual(.integer, result.type)
88+
XCTAssertEqual(.boolean, result.type)
8989
XCTAssertEqual(.integer, type(for: 1, in: result))
9090
XCTAssertEqual("bar", name(for: 1, in: result))
9191
}
@@ -153,37 +153,37 @@ class TypeCheckerTests: XCTestCase {
153153

154154
func testInRowSingleValue() throws {
155155
let result = try result(for: ":bar IN (1)")
156-
XCTAssertEqual(.integer, result.type)
156+
XCTAssertEqual(.boolean, result.type)
157157
XCTAssertEqual(.integer, type(for: "bar", in: result))
158158
}
159159

160160
func testInRowMultipleValues() throws {
161161
let result = try result(for: ":bar IN (1, 2.0)")
162-
XCTAssertEqual(.integer, result.type)
162+
XCTAssertEqual(.boolean, result.type)
163163
XCTAssertEqual(.real, type(for: "bar", in: result))
164164
}
165165

166166
func testInRowManyTypesUnUnifiable() throws {
167167
let result = try result(for: ":bar IN (1, 'Foo', 2.0)")
168-
XCTAssertEqual(.integer, result.type)
168+
XCTAssertEqual(.boolean, result.type)
169169
XCTAssert(!result.diagnostics.elements.isEmpty)
170170
}
171171

172172
func testInRowInferInputAsRow() throws {
173173
let result = try result(for: "1 IN :bar")
174-
XCTAssertEqual(.integer, result.type)
174+
XCTAssertEqual(.boolean, result.type)
175175
XCTAssertEqual(.row(.unknown(.integer)), type(for: "bar", in: result))
176176
}
177177

178178
func testNotIn() throws {
179179
let result = try result(for: ":bar NOT IN (1, 2)")
180-
XCTAssertEqual(.integer, result.type)
180+
XCTAssertEqual(.boolean, result.type)
181181
XCTAssertEqual(.integer, type(for: "bar", in: result))
182182
}
183183

184184
func testNull() throws {
185185
let result = try result(for: ":bar > 1 OR :bar == NULL")
186-
XCTAssertEqual(.integer, result.type)
186+
XCTAssertEqual(.boolean, result.type)
187187
XCTAssertEqual(.optional(.integer), type(for: "bar", in: result))
188188
}
189189

0 commit comments

Comments
 (0)