Skip to content

Commit 6d2b2a5

Browse files
committed
Improve type error messages
1 parent bd1118b commit 6d2b2a5

File tree

4 files changed

+75
-109
lines changed

4 files changed

+75
-109
lines changed

ShapeScript/Interpreter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public extension RuntimeError {
9696
}
9797
return "Unexpected symbol '\(name)'"
9898
case let .unknownMember(name, type, _):
99-
return "Member '\(name)' not found for \(type)"
99+
return "Member '\(name)' not found in \(type)"
100100
case let .invalidIndex(index, _):
101101
return "Index \(index.logDescription) out of bounds"
102102
case let .unknownFont(name, _):

ShapeScript/Types.swift

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -171,39 +171,6 @@ extension ValueType {
171171
}
172172
}
173173

174-
var errorDescription: String {
175-
switch self {
176-
case .color: return "color"
177-
case .texture: return "texture"
178-
case .material: return "material"
179-
case .font: return "font"
180-
case .boolean: return "boolean"
181-
case .number: return "number"
182-
case .radians: return "angle in radians"
183-
case .halfturns: return "angle in half-turns"
184-
case .vector, .list(.number): return "vector"
185-
case .size: return "size"
186-
case .rotation: return "rotation"
187-
case .string: return "string"
188-
case .text: return "text"
189-
case .path: return "path"
190-
case .mesh: return "mesh"
191-
case .polygon: return "polygon"
192-
case .point: return "point"
193-
case .range: return "range"
194-
case .partialRange: return "partial range"
195-
case .bounds: return "bounds"
196-
case .any: return "any"
197-
case let .tuple(types) where types.count == 1:
198-
return types[0].errorDescription
199-
case .tuple([]): return "empty tuple"
200-
case .tuple, .list: return "tuple"
201-
case let .union(types):
202-
return types.sorted().errorDescription
203-
case .object: return "object"
204-
}
205-
}
206-
207174
func isSubtype(of type: ValueType) -> Bool {
208175
switch (self, type) {
209176
case (_, .any):
@@ -223,11 +190,58 @@ extension ValueType {
223190
return self == type
224191
}
225192
}
226-
}
227193

228-
extension [ValueType] {
229194
var errorDescription: String {
230-
let types = map(\.errorDescription)
195+
description(pluralized: false)
196+
}
197+
198+
fileprivate func description(pluralized: Bool) -> String {
199+
switch self {
200+
case .color: return "color\(pluralized ? "s" : "")"
201+
case .texture: return "texture\(pluralized ? "s" : "")"
202+
case .material: return "material\(pluralized ? "s" : "")"
203+
case .font: return "font\(pluralized ? "s" : "")"
204+
case .boolean: return "boolean\(pluralized ? "s" : "")"
205+
case .number: return "number\(pluralized ? "s" : "")"
206+
case .radians: return "angle\(pluralized ? "s" : "") in radians"
207+
case .halfturns: return "angle\(pluralized ? "s" : "") in half-turns"
208+
case .vector: return "vector\(pluralized ? "s" : "")"
209+
case .size: return "size\(pluralized ? "s" : "")"
210+
case .rotation: return "rotation\(pluralized ? "s" : "")"
211+
case .string: return "string\(pluralized ? "s" : "")"
212+
case .text, .list(.text): return "text"
213+
case .path: return "path\(pluralized ? "s" : "")"
214+
case .mesh: return "mesh\(pluralized ? "es" : "")"
215+
case .polygon: return "polygon\(pluralized ? "s" : "")"
216+
case .point: return "point\(pluralized ? "s" : "")"
217+
case .range: return "range\(pluralized ? "s" : "")"
218+
case .partialRange: return "partial range\(pluralized ? "s" : "")"
219+
case .bounds: return "bounds"
220+
case .any: return "any"
221+
case let .tuple(types) where types.count == 1:
222+
return types[0].description(pluralized: pluralized)
223+
case .tuple([]): return "empty tuple\(pluralized ? "s" : "")"
224+
case let .tuple(types) where Set(types) == [.number] && (2 ... 3).contains(types.count):
225+
return "vector"
226+
case let .tuple(types) where Set(types).count == 1:
227+
return "list\(pluralized ? "s" : "") of \(types[0].description(pluralized: true))"
228+
case .tuple:
229+
// TODO: list the types?
230+
return "tuple\(pluralized ? "s" : "")"
231+
case .list(.any): return "list"
232+
case .list(.number): return "vector"
233+
case let .list(type):
234+
return "list\(pluralized ? "s" : "") of \(type.description(pluralized: true))"
235+
case let .union(types):
236+
return types.sorted().description(pluralized: pluralized)
237+
case .object: return "object\(pluralized ? "s" : "")"
238+
}
239+
}
240+
}
241+
242+
private extension [ValueType] {
243+
func description(pluralized: Bool) -> String {
244+
let types = Set(self).sorted().map { $0.description(pluralized: pluralized) }
231245
switch types.count {
232246
case 1:
233247
return types[0]

ShapeScriptTests/InterpreterTests.swift

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ final class InterpreterTests: XCTestCase {
972972
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
973973
let error = try? XCTUnwrap(error as? RuntimeError)
974974
XCTAssertEqual(error, RuntimeError(.typeMismatch(
975-
for: "color", expected: "color", got: "tuple"
975+
for: "color", expected: "color", got: "list of numbers"
976976
), at: range))
977977
}
978978
}
@@ -2426,7 +2426,7 @@ final class InterpreterTests: XCTestCase {
24262426
XCTAssertEqual(error?.message, "Type mismatch")
24272427
XCTAssertEqual(error, RuntimeError(.typeMismatch(
24282428
for: "loop bounds",
2429-
expected: "range or tuple",
2429+
expected: "range or list",
24302430
got: "number"
24312431
), at: range))
24322432
}
@@ -2440,7 +2440,7 @@ final class InterpreterTests: XCTestCase {
24402440
XCTAssertEqual(error?.message, "Type mismatch")
24412441
XCTAssertEqual(error, RuntimeError(.typeMismatch(
24422442
for: "loop bounds",
2443-
expected: "range or tuple",
2443+
expected: "range or list",
24442444
got: "string"
24452445
), at: range))
24462446
}
@@ -2550,10 +2550,10 @@ final class InterpreterTests: XCTestCase {
25502550
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
25512551
let error = try? XCTUnwrap(error as? RuntimeError)
25522552
XCTAssertEqual(error?.message, "Type mismatch")
2553-
XCTAssertEqual(error?.hint, "The loop bounds should be a range or tuple, not a color.")
2553+
XCTAssertEqual(error?.hint, "The loop bounds should be a range or list, not a color.")
25542554
XCTAssertEqual(error, RuntimeError(.typeMismatch(
25552555
for: "loop bounds",
2556-
expected: "range or tuple",
2556+
expected: "range or list",
25572557
got: "color"
25582558
), at: range))
25592559
}
@@ -3653,19 +3653,17 @@ final class InterpreterTests: XCTestCase {
36533653
XCTAssertEqual(delegate.log, [3.0, -6.0])
36543654
}
36553655

3656-
func testNonNumericStringTupleScalarMultiply() {
3656+
func testNonNumericStringTupleScalarMultiply() throws {
36573657
let program = "print (\"foo\" \"bar\") * 3"
3658+
let range = try XCTUnwrap(program.range(of: "(\"foo\" \"bar\")"))
36583659
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
36593660
let error = try? XCTUnwrap(error as? RuntimeError)
3660-
guard case .typeMismatch(
3661+
XCTAssertEqual(error, RuntimeError(.typeMismatch(
36613662
for: "*",
36623663
index: 0,
36633664
expected: "number, texture, or vector",
3664-
got: "tuple"
3665-
)? = error?.type else {
3666-
XCTFail()
3667-
return
3668-
}
3665+
got: "list of strings"
3666+
), at: range))
36693667
}
36703668
}
36713669

ShapeScriptTests/MemberTests.swift

Lines changed: 14 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ final class MemberTests: XCTestCase {
3131
let program = "print (1 2 3 4).x"
3232
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
3333
let error = try? XCTUnwrap(error as? RuntimeError)
34-
guard case .unknownMember("x", of: "tuple", _)? = error?.type else {
35-
XCTFail()
36-
return
37-
}
34+
XCTAssertEqual(error?.message, "Member 'x' not found in list of numbers")
3835
}
3936
}
4037

@@ -201,25 +198,17 @@ final class MemberTests: XCTestCase {
201198
let program = "print (1 2 3 4 5).red"
202199
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
203200
let error = try? XCTUnwrap(error as? RuntimeError)
204-
XCTAssertEqual(error?.message, "Member 'red' not found for tuple")
201+
XCTAssertEqual(error?.message, "Member 'red' not found in list of numbers")
205202
XCTAssertNotEqual(error?.suggestion, "red")
206-
guard case .unknownMember("red", of: "tuple", _) = error?.type else {
207-
XCTFail()
208-
return
209-
}
210203
}
211204
}
212205

213206
func testEmptyTupleColorLookup() {
214207
let program = "print ().blue"
215208
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
216209
let error = try? XCTUnwrap(error as? RuntimeError)
217-
XCTAssertEqual(error?.message, "Member 'blue' not found for empty tuple")
210+
XCTAssertEqual(error?.message, "Member 'blue' not found in empty tuple")
218211
XCTAssertNotEqual(error?.suggestion, "blue")
219-
guard case .unknownMember("blue", of: "empty tuple", _) = error?.type else {
220-
XCTFail()
221-
return
222-
}
223212
}
224213
}
225214

@@ -241,35 +230,23 @@ final class MemberTests: XCTestCase {
241230
let program = "print (\"foo\" \"bar\").red"
242231
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
243232
let error = try? XCTUnwrap(error as? RuntimeError)
244-
XCTAssertEqual(error?.message, "Member 'red' not found for tuple")
245-
guard case .unknownMember("red", of: "tuple", _)? = error?.type else {
246-
XCTFail()
247-
return
248-
}
233+
XCTAssertEqual(error?.message, "Member 'red' not found in list of strings")
249234
}
250235
}
251236

252237
func testTupleNonexistentLookup() {
253238
let program = "print (1 2).foo"
254239
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
255240
let error = try? XCTUnwrap(error as? RuntimeError)
256-
XCTAssertEqual(error?.message, "Member 'foo' not found for tuple")
257-
guard case .unknownMember("foo", of: "tuple", _) = error?.type else {
258-
XCTFail()
259-
return
260-
}
241+
XCTAssertEqual(error?.message, "Member 'foo' not found in vector")
261242
}
262243
}
263244

264245
func testColorWidthLookup() {
265246
let program = "color 1 0.5\nprint color.width"
266247
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
267248
let error = try? XCTUnwrap(error as? RuntimeError)
268-
XCTAssertEqual(error?.message, "Member 'width' not found for color")
269-
guard case .unknownMember("width", of: "color", _)? = error?.type else {
270-
XCTFail()
271-
return
272-
}
249+
XCTAssertEqual(error?.message, "Member 'width' not found in color")
273250
}
274251
}
275252

@@ -281,11 +258,7 @@ final class MemberTests: XCTestCase {
281258
"""
282259
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
283260
let error = try? XCTUnwrap(error as? RuntimeError)
284-
XCTAssertEqual(error?.message, "Member 'x' not found for rotation")
285-
guard case .unknownMember("x", of: "rotation", _)? = error?.type else {
286-
XCTFail()
287-
return
288-
}
261+
XCTAssertEqual(error?.message, "Member 'x' not found in rotation")
289262
}
290263
}
291264

@@ -312,7 +285,7 @@ final class MemberTests: XCTestCase {
312285
let program = "define col 1 0.5\nprint col.scond"
313286
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
314287
let error = try? XCTUnwrap(error as? RuntimeError)
315-
XCTAssertEqual(error?.message, "Member 'scond' not found for tuple")
288+
XCTAssertEqual(error?.message, "Member 'scond' not found in vector")
316289
XCTAssertEqual(error?.hint, "Did you mean 'second'?")
317290
}
318291
}
@@ -321,20 +294,16 @@ final class MemberTests: XCTestCase {
321294
let program = "define col 1 0.5\nprint col.third"
322295
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
323296
let error = try? XCTUnwrap(error as? RuntimeError)
324-
XCTAssertEqual(error?.message, "Member 'third' not found for tuple")
297+
XCTAssertEqual(error?.message, "Member 'third' not found in vector")
325298
XCTAssertEqual(error?.hint, "Valid range is 'first' to 'second'.")
326-
guard case .unknownMember("third", of: "tuple", _)? = error?.type else {
327-
XCTFail()
328-
return
329-
}
330299
}
331300
}
332301

333302
func testTupleMisspelledOutOfBoundsOrdinalLookup() {
334303
let program = "define col 1 0.5\nprint col.thidr"
335304
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
336305
let error = try? XCTUnwrap(error as? RuntimeError)
337-
XCTAssertEqual(error?.message, "Member 'thidr' not found for tuple")
306+
XCTAssertEqual(error?.message, "Member 'thidr' not found in vector")
338307
XCTAssertEqual(error?.hint, "Did you mean 'third'?")
339308
}
340309
}
@@ -485,11 +454,7 @@ final class MemberTests: XCTestCase {
485454
"""
486455
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
487456
let error = try? XCTUnwrap(error as? RuntimeError)
488-
XCTAssertEqual(error?.message, "Member 'polygons' not found for path")
489-
guard case .unknownMember("polygons", of: "path", _)? = error?.type else {
490-
XCTFail()
491-
return
492-
}
457+
XCTAssertEqual(error?.message, "Member 'polygons' not found in path")
493458
}
494459
}
495460

@@ -499,11 +464,7 @@ final class MemberTests: XCTestCase {
499464
"""
500465
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
501466
let error = try? XCTUnwrap(error as? RuntimeError)
502-
XCTAssertEqual(error?.message, "Member 'polygons' not found for camera")
503-
guard case .unknownMember("polygons", of: "camera", _)? = error?.type else {
504-
XCTFail()
505-
return
506-
}
467+
XCTAssertEqual(error?.message, "Member 'polygons' not found in camera")
507468
}
508469
}
509470

@@ -513,11 +474,7 @@ final class MemberTests: XCTestCase {
513474
"""
514475
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
515476
let error = try? XCTUnwrap(error as? RuntimeError)
516-
XCTAssertEqual(error?.message, "Member 'x' not found for mesh")
517-
guard case .unknownMember("x", of: "mesh", _)? = error?.type else {
518-
XCTFail()
519-
return
520-
}
477+
XCTAssertEqual(error?.message, "Member 'x' not found in mesh")
521478
}
522479
}
523480

@@ -727,10 +684,7 @@ final class MemberTests: XCTestCase {
727684
let program = "print (1 0)[\"w\"]"
728685
XCTAssertThrowsError(try evaluate(parse(program), delegate: nil)) { error in
729686
let error = try? XCTUnwrap(error as? RuntimeError)
730-
guard case .unknownMember("w", of: "tuple", _)? = error?.type else {
731-
XCTFail()
732-
return
733-
}
687+
XCTAssertEqual(error?.message, "Member 'w' not found in vector")
734688
}
735689
}
736690

0 commit comments

Comments
 (0)