Skip to content

Commit f791734

Browse files
committed
Replaced constraints with ty var kinds
1 parent c36ef69 commit f791734

File tree

5 files changed

+126
-77
lines changed

5 files changed

+126
-77
lines changed

Sources/Compiler/Context.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
// Created by Wes Wickwire on 2/23/25.
66
//
77

8-
9-
108
struct Context {
119
private(set) var schema = Schema()
12-
private var types: [SyntaxId: Type] = [:]
1310

11+
private var types: [SyntaxId: Type] = [:]
1412

13+
private var signatures: [SyntaxId: Signature] = [:]
1514
}

Sources/Compiler/Diagnostic.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,12 @@ extension Diagnostic {
151151
) -> Diagnostic {
152152
return Diagnostic("Table '\(table)' already has a primary key", at: range)
153153
}
154+
155+
static func unableToUnify(
156+
_ t1: Type,
157+
with t2: Type,
158+
at range: Range<Substring.Index>
159+
) -> Diagnostic {
160+
return Diagnostic("Unable to unify types '\(t1)' and '\(t2)'", at: range)
161+
}
154162
}

Sources/Compiler/Sema/Type.swift

Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,23 @@
77

88
import OrderedCollections
99

10+
/// A type, these are not just the types define in the tables
11+
/// but rather any value or function that can exist within
12+
/// an expression/statement.
1013
public enum Type: Equatable, CustomStringConvertible, Sendable {
14+
/// A named type, these are the values defined in the columns.
1115
case nominal(Substring)
16+
/// A placeholder for a type that needs to be solved for
1217
case `var`(TypeVariable)
18+
/// A function
1319
indirect case fn(params: [Type], ret: Type)
20+
/// A row is a value from a `SELECT` statement or a group `(?, ?, ?)`
1421
case row(Row)
22+
/// A type that might be null.
1523
indirect case optional(Type)
24+
/// A type that has been aliased. These are not in SQL by default
25+
/// but are from the layer on top that we are adding so a user
26+
/// can replace a `INTEGER` with a `Bool`
1627
indirect case alias(Type, Substring)
1728
/// There was an error somewhere in the analysis. We can just return
1829
/// an `error` type and continue the analysis. So if the user makes up
@@ -31,7 +42,10 @@ public enum Type: Equatable, CustomStringConvertible, Sendable {
3142
/// a FROM or JOIN. Unnamed would be from a subquery or
3243
/// a row expression `(?, ?, ?)`.
3344
public enum Row: Equatable, Sendable, ExpressibleByArrayLiteral {
45+
/// A row that has names for each value
3446
case named(OrderedDictionary<Substring, Type>)
47+
/// A row that does not have names. These can come from
48+
/// using `VALUES (1, 2, 3)`.
3549
case unnamed([Type])
3650
/// This is a special row that we don't know the inner types of.
3751
/// It assumes that all types in it are the same type and of an
@@ -157,6 +171,30 @@ public enum Type: Equatable, CustomStringConvertible, Sendable {
157171
case (.error, _), (_, .error):
158172
// Already had an upstream error so no need to emit any more diagnostics
159173
return [:]
174+
case let (.var(tv1), .var(tv2)):
175+
// Unify to type variables.
176+
// We need to prioritize what gets substituded for what
177+
// by its kind.
178+
// So if we get an `integer` and `float`, we want to promote
179+
// the `integer` to a `float`, so we sub the int for the float
180+
switch (tv1.kind, tv2.kind) {
181+
case (.general, _):
182+
// General can always be substitued out
183+
return [tv1: other]
184+
case (_, .general):
185+
// General can always be substitued out
186+
return [tv2: self]
187+
case (.float, .integer):
188+
// self: float, other: int
189+
// substitute other for self
190+
return [tv2: self]
191+
case (.integer, .float):
192+
// self: int, other: float
193+
// substitute self for other
194+
return [tv1: other]
195+
default:
196+
return [tv1: other]
197+
}
160198
case let (.var(tv), ty):
161199
return [tv: ty]
162200
case let (ty, .var(tv)):
@@ -167,7 +205,7 @@ public enum Type: Equatable, CustomStringConvertible, Sendable {
167205
return [:]
168206
case (.nominal, .nominal):
169207
guard self != other else { return [:] }
170-
diagnostics.add(.init("Unable to unify types '\(self)' and '\(other)'", at: range))
208+
diagnostics.add(.unableToUnify(self, with: other, at: range))
171209
return [:]
172210
case let (.optional(t1), t2):
173211
return t1.unify(with: t2, at: range, diagnostics: &diagnostics)
@@ -187,22 +225,22 @@ public enum Type: Equatable, CustomStringConvertible, Sendable {
187225
if row.count == 1, let first = row.first {
188226
return first.unify(with: t, at: range, diagnostics: &diagnostics)
189227
} else {
190-
diagnostics.add(.init("Unable to unify types '\(self)' and '\(other)'", at: range))
228+
diagnostics.add(.unableToUnify(self, with: other, at: range))
191229
return [:]
192230
}
193231
case let (t, .row(row)):
194232
if row.count == 1, let first = row.first {
195233
return first.unify(with: t, at: range, diagnostics: &diagnostics)
196234
} else {
197-
diagnostics.add(.init("Unable to unify types '\(self)' and '\(other)'", at: range))
235+
diagnostics.add(.unableToUnify(self, with: other, at: range))
198236
return [:]
199237
}
200-
case let (.alias(t1, a), t2):
238+
case let (.alias(t1, _), t2):
201239
return t1.unify(with: t2, at: range, diagnostics: &diagnostics)
202-
case let (t1, .alias(t2, a)):
240+
case let (t1, .alias(t2, _)):
203241
return t2.unify(with: t1, at: range, diagnostics: &diagnostics)
204242
default:
205-
diagnostics.add(.init("Unable to unify types '\(self)' and '\(other)'", at: range))
243+
diagnostics.add(.unableToUnify(self, with: other, at: range))
206244
return [:]
207245
}
208246
}
@@ -247,39 +285,73 @@ public enum Type: Equatable, CustomStringConvertible, Sendable {
247285

248286
return sub
249287
}
288+
289+
private func validateCanUnify(
290+
with tvKind: TypeVariable.Kind,
291+
diagnostics: inout Diagnostics,
292+
at range: Range<Substring.Index>
293+
) {
294+
switch tvKind {
295+
case .general:
296+
return
297+
case .integer:
298+
switch self {
299+
case .int, .integer, .real: return
300+
default: diagnostics.add(.unableToUnify(self, with: .integer, at: range))
301+
}
302+
case .float:
303+
switch self {
304+
case .int, .integer, .real: return
305+
default: diagnostics.add(.unableToUnify(self, with: .real, at: range))
306+
}
307+
}
308+
}
250309
}
251310

311+
/// A type variable is a type placeholder for an expression who's type we need to solve.
252312
public struct TypeVariable: Hashable, CustomStringConvertible, ExpressibleByIntegerLiteral, Sendable {
313+
/// The unique integer associated with the variable.
314+
/// These are just incremented as they are created.
253315
let n: Int
316+
/// What kind or group this type variable belongs too.
317+
let kind: Kind
254318

255-
init(_ n: Int) {
319+
/// There are different type of type variables.
320+
/// Each are spawned from different usages.
321+
enum Kind {
322+
/// `integer` is any type variable from an integer literal e.g. `0`
323+
case integer
324+
/// `float` is any type variable from an float literal e.g. `0.0`
325+
case float
326+
/// `general` is any type variable that does not fall into the above
327+
case general
328+
}
329+
330+
init(_ n: Int, kind: Kind) {
256331
self.n = n
332+
self.kind = kind
257333
}
258334

259335
public init(integerLiteral value: Int) {
260336
self.n = value
337+
self.kind = .general
261338
}
262339

263-
public var description: String {
264-
return "τ\(n)"
340+
/// The type to use for an expression if no concrete type was
341+
/// found in the solution.
342+
///
343+
/// e.g. `1 + 1`, each literal is not bound to a concrete type
344+
/// like a column. So each would still be a variable after the
345+
/// substitution is applied. In which a default is needed.
346+
var defaultType: Type {
347+
return switch kind {
348+
case .general: .any
349+
case .integer: .integer
350+
case .float: .real
351+
}
265352
}
266-
}
267-
268-
// TODO: Need a better name for this. TypeConstraints vs Constraints is confusing
269-
typealias Constraints = [TypeVariable: TypeConstraints]
270-
271-
/// Any type constraints that may exist on the type variable.
272-
/// SQL is not a full language and users cannot create their
273-
/// own interfaces so a simple option set will do since it is
274-
/// a finite number.
275-
struct TypeConstraints: OptionSet, Hashable {
276-
let rawValue: UInt8
277353

278-
static let numeric = TypeConstraints(rawValue: 1 << 0)
279-
}
280-
281-
extension Constraints {
282-
func merging(_ other: Constraints) -> Constraints {
283-
return merging(other, uniquingKeysWith: { $0.union($1) })
354+
public var description: String {
355+
return "τ\(n)"
284356
}
285357
}

Sources/Compiler/Sema/TypeChecker.swift

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
import OrderedCollections
99

10+
struct InferenceContext {
11+
12+
}
13+
1014
struct TypeChecker {
1115
/// The environment in which the query executes. Any joined in tables
1216
/// will be added to this.
@@ -22,10 +26,6 @@ struct TypeChecker {
2226
/// the final type. The overall substitution will have to be applied
2327
/// to the type.
2428
private var parameterTypes: [BindParameterSyntax.Index: Type] = [:]
25-
/// Any constraints over a type. These are not constraints as in a
26-
/// constraint based inference algorithm but rather constraints on a type
27-
/// like type classes, protocols, or interfaces.
28-
private var constraints: Constraints = [:]
2929
/// We are not only inferring types but potential names for the parameters.
3030
/// Any result will be added here
3131
private var parameterNames: [BindParameterSyntax.Index: Substring] = [:]
@@ -62,15 +62,12 @@ struct TypeChecker {
6262
sub: Substitution,
6363
outputCardinality: Signature.Cardinality
6464
) -> Signature {
65-
let constraints = finalizeConstraints(with: sub)
66-
6765
return Signature(
6866
parameters: parameterTypes.reduce(into: [:]) { params, value in
6967
params[value.key] = Signature.Parameter(
7068
type: finalType(
7169
for: value.value,
72-
substitution: sub,
73-
constraints: constraints
70+
substitution: sub
7471
),
7572
index: value.key,
7673
name: parameterNames[value.key]
@@ -79,8 +76,7 @@ struct TypeChecker {
7976
output: ty.map { ty in
8077
finalType(
8178
for: ty.apply(sub),
82-
substitution: sub,
83-
constraints: constraints
79+
substitution: sub
8480
)
8581
},
8682
outputCardinality: outputCardinality
@@ -92,59 +88,35 @@ struct TypeChecker {
9288
/// found one will be guessed from the constraints.
9389
private func finalType(
9490
for ty: Type,
95-
substitution: Substitution,
96-
constraints: Constraints
91+
substitution: Substitution
9792
) -> Type {
9893
let ty = ty.apply(substitution)
9994

10095
switch ty.apply(substitution) {
10196
case let .var(tv):
102-
// The type variable was never bound to a concrete type.
103-
// Check if the constraints gives any clues about a default type
104-
// if none just assume `ANY`
105-
if let constraints = constraints[tv], constraints.contains(.numeric) {
106-
return .integer
107-
}
108-
return .any
97+
// Was never bound to a concrete type. Try to guess
98+
// based of the ty var kind
99+
return tv.defaultType
109100
case let .row(tys):
110101
// Finalize all of the inner types in the row.
111102
return .row(tys.mapTypes {
112103
finalType(
113104
for: $0,
114-
substitution: substitution,
115-
constraints: constraints
105+
substitution: substitution
116106
)
117107
})
118108
default:
119109
return ty
120110
}
121111
}
122112

123-
/// Applies the substitution to the overall constraints
124-
/// so it is the final type to in the map
125-
private mutating func finalizeConstraints(
126-
with substitution: Substitution
127-
) -> Constraints {
128-
var result: [TypeVariable: TypeConstraints] = [:]
129-
130-
for (tv, constraints) in constraints {
131-
let ty = Type.var(tv).apply(substitution)
132-
133-
if case let .var(tv) = ty {
134-
result[tv] = constraints
135-
} else {
136-
// TODO: If it is a non type variable we need to validate
137-
// the type meets the constraints requirements.
138-
}
139-
}
140-
141-
return result
142-
}
143-
144113
/// Creates a fresh new unique type variable
145-
private mutating func freshTyVar(for param: BindParameterSyntax? = nil) -> TypeVariable {
114+
private mutating func freshTyVar(
115+
for param: BindParameterSyntax? = nil,
116+
kind: TypeVariable.Kind = .general
117+
) -> TypeVariable {
146118
defer { tyVarCounter += 1 }
147-
let ty = TypeVariable(tyVarCounter)
119+
let ty = TypeVariable(tyVarCounter, kind: kind)
148120
if let param {
149121
parameterTypes[param.index] = .var(ty)
150122
}
@@ -230,7 +202,6 @@ struct TypeChecker {
230202
diagnostics = inferrer.diagnostics
231203
tyVarCounter = inferrer.tyVarCounter
232204
parameterTypes = inferrer.parameterTypes
233-
constraints = inferrer.constraints
234205
return result
235206
}
236207
}
@@ -240,8 +211,7 @@ extension TypeChecker: ExprSyntaxVisitor {
240211
switch expr.kind {
241212
case let .numeric(_, isInt):
242213
if isInt {
243-
let tv = freshTyVar()
244-
constraints[tv] = .numeric
214+
let tv = freshTyVar(kind: isInt ? .integer : .float)
245215
return (.var(tv), [:], .none)
246216
} else {
247217
return (.real, [:], .none)

Tests/CompilerTests/TypeCheckerTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class TypeCheckerTests: XCTestCase {
171171

172172
func scope(table: String, schema: String) throws -> Environment {
173173
var compiler = SchemaCompiler()
174-
compiler.compile(schema)
174+
_ = compiler.compile(schema)
175175
guard let table = compiler.schema[table[...]] else { fatalError("'table' provided not in 'schema'") }
176176
var env = Environment()
177177
env.upsert(table.name, ty: table.type)

0 commit comments

Comments
 (0)