dhall 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/COPYING +676 -0
- data/README.md +283 -0
- data/bin/json-to-dhall +8 -0
- data/bin/yaml-to-dhall +8 -0
- data/dhall.gemspec +36 -0
- data/lib/dhall.rb +37 -0
- data/lib/dhall/as_dhall.rb +195 -0
- data/lib/dhall/ast.rb +1559 -0
- data/lib/dhall/binary.rb +277 -0
- data/lib/dhall/builtins.rb +425 -0
- data/lib/dhall/normalize.rb +420 -0
- data/lib/dhall/parser.citrus +500 -0
- data/lib/dhall/parser.rb +601 -0
- data/lib/dhall/resolve.rb +383 -0
- data/lib/dhall/typecheck.rb +1209 -0
- data/lib/dhall/util.rb +130 -0
- data/lib/dhall/visitor.rb +23 -0
- metadata +163 -0
@@ -0,0 +1,383 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "promise.rb"
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
require "dhall/ast"
|
8
|
+
require "dhall/binary"
|
9
|
+
require "dhall/util"
|
10
|
+
|
11
|
+
module Dhall
|
12
|
+
class ImportFailedException < StandardError; end
|
13
|
+
class ImportBannedException < ImportFailedException; end
|
14
|
+
class ImportLoopException < ImportBannedException; end
|
15
|
+
|
16
|
+
module Resolvers
|
17
|
+
ReadPathSources = lambda do |sources|
|
18
|
+
sources.map do |source|
|
19
|
+
Promise.resolve(nil).then { source.pathname.binread }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
PreflightCORS = lambda do |source, parent_origin|
|
24
|
+
uri = source.uri
|
25
|
+
if parent_origin != "localhost" && parent_origin != source.origin
|
26
|
+
req = Net::HTTP::Options.new(uri)
|
27
|
+
req["Origin"] = parent_origin
|
28
|
+
req["Access-Control-Request-Method"] = "GET"
|
29
|
+
req["Access-Control-Request-Headers"] =
|
30
|
+
source.headers.map { |h| h.fetch("header").to_s }.join(",")
|
31
|
+
r = Net::HTTP.start(
|
32
|
+
uri.hostname,
|
33
|
+
uri.port,
|
34
|
+
use_ssl: uri.scheme == "https"
|
35
|
+
) { |http| http.request(req) }
|
36
|
+
|
37
|
+
raise ImportFailedException, source if r.code != "200"
|
38
|
+
unless r["Access-Control-Allow-Origin"] == parent_origin ||
|
39
|
+
r["Access-Control-Allow-Origin"] == "*"
|
40
|
+
raise ImportBannedException, source
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
ReadHttpSources = lambda do |sources, parent_origin|
|
46
|
+
sources.map do |source|
|
47
|
+
Promise.resolve(nil).then do
|
48
|
+
PreflightCORS.call(source, parent_origin)
|
49
|
+
uri = source.uri
|
50
|
+
req = Net::HTTP::Get.new(uri)
|
51
|
+
source.headers.each do |header|
|
52
|
+
req[header.fetch("header").to_s] = header.fetch("value").to_s
|
53
|
+
end
|
54
|
+
r = Net::HTTP.start(
|
55
|
+
uri.hostname,
|
56
|
+
uri.port,
|
57
|
+
use_ssl: uri.scheme == "https"
|
58
|
+
) { |http| http.request(req) }
|
59
|
+
|
60
|
+
raise ImportFailedException, source if r.code != "200"
|
61
|
+
r.body
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
RejectSources = lambda do |sources|
|
67
|
+
sources.map do |source|
|
68
|
+
Promise.new.reject(ImportBannedException.new(source))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class ReadPathAndIPFSSources
|
73
|
+
def initialize(
|
74
|
+
path_reader: ReadPathSources,
|
75
|
+
http_reader: ReadHttpSources,
|
76
|
+
https_reader: http_reader,
|
77
|
+
public_gateway: "cloudflare-ipfs.com"
|
78
|
+
)
|
79
|
+
@path_reader = path_reader
|
80
|
+
@http_reader = http_reader
|
81
|
+
@https_reader = https_reader
|
82
|
+
@public_gateway = public_gateway
|
83
|
+
end
|
84
|
+
|
85
|
+
def arity
|
86
|
+
1
|
87
|
+
end
|
88
|
+
|
89
|
+
def call(sources)
|
90
|
+
@path_reader.call(sources).map.with_index do |promise, idx|
|
91
|
+
source = sources[idx]
|
92
|
+
if source.is_a?(Import::AbsolutePath) &&
|
93
|
+
["ipfs", "ipns"].include?(source.path.first)
|
94
|
+
gateway_fallback(source, promise)
|
95
|
+
else
|
96
|
+
promise
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_proc
|
102
|
+
method(:call).to_proc
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def gateway_fallback(source, promise)
|
108
|
+
promise.catch {
|
109
|
+
@http_reader.call([
|
110
|
+
source.to_uri(Import::Http, "localhost:8000")
|
111
|
+
], "localhost").first
|
112
|
+
}.catch do
|
113
|
+
@https_reader.call([
|
114
|
+
source.to_uri(Import::Https, @public_gateway)
|
115
|
+
], "localhost").first
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class ResolutionSet
|
121
|
+
def initialize(reader)
|
122
|
+
@reader = reader
|
123
|
+
@parents = []
|
124
|
+
@set = Hash.new { |h, k| h[k] = [] }
|
125
|
+
end
|
126
|
+
|
127
|
+
def register(source)
|
128
|
+
p = Promise.new
|
129
|
+
if @parents.include?(source)
|
130
|
+
p.reject(ImportLoopException.new(source))
|
131
|
+
else
|
132
|
+
@set[source] << p
|
133
|
+
end
|
134
|
+
p
|
135
|
+
end
|
136
|
+
|
137
|
+
def resolutions
|
138
|
+
sources, promises = @set.to_a.transpose
|
139
|
+
[Array(sources), Array(promises)]
|
140
|
+
end
|
141
|
+
|
142
|
+
def reader
|
143
|
+
lambda do |sources|
|
144
|
+
if @reader.arity == 2
|
145
|
+
@reader.call(sources, @parents.last&.origin || "localhost")
|
146
|
+
else
|
147
|
+
@reader.call(sources)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def child(parent_source)
|
153
|
+
dup.tap do |c|
|
154
|
+
c.instance_eval do
|
155
|
+
@parents = @parents.dup + [parent_source]
|
156
|
+
@set = Hash.new { |h, k| h[k] = [] }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Standard
|
163
|
+
def initialize(
|
164
|
+
path_reader: ReadPathSources,
|
165
|
+
http_reader: ReadHttpSources,
|
166
|
+
https_reader: http_reader
|
167
|
+
)
|
168
|
+
@path_resolutions = ResolutionSet.new(path_reader)
|
169
|
+
@http_resolutions = ResolutionSet.new(http_reader)
|
170
|
+
@https_resolutions = ResolutionSet.new(https_reader)
|
171
|
+
@cache = {}
|
172
|
+
end
|
173
|
+
|
174
|
+
def cache_fetch(key, &fallback)
|
175
|
+
@cache.fetch(key) do
|
176
|
+
Promise.resolve(nil).then(&fallback).then do |result|
|
177
|
+
@cache[key] = result
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def resolve_path(path_source)
|
183
|
+
@path_resolutions.register(path_source)
|
184
|
+
end
|
185
|
+
|
186
|
+
def resolve_http(http_source)
|
187
|
+
http_source.headers.resolve(
|
188
|
+
resolver: self,
|
189
|
+
relative_to: Dhall::Import::RelativePath.new
|
190
|
+
).then do |headers|
|
191
|
+
@http_resolutions.register(
|
192
|
+
http_source.with(headers: headers.normalize)
|
193
|
+
)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def resolve_https(https_source)
|
198
|
+
https_source.headers.resolve(
|
199
|
+
resolver: self,
|
200
|
+
relative_to: Dhall::Import::RelativePath.new
|
201
|
+
).then do |headers|
|
202
|
+
@https_resolutions.register(
|
203
|
+
https_source.with(headers: headers.normalize)
|
204
|
+
)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def finish!
|
209
|
+
[
|
210
|
+
@path_resolutions,
|
211
|
+
@http_resolutions,
|
212
|
+
@https_resolutions
|
213
|
+
].each do |rset|
|
214
|
+
Util.match_result_promises(*rset.resolutions, &rset.reader)
|
215
|
+
end
|
216
|
+
freeze
|
217
|
+
end
|
218
|
+
|
219
|
+
def child(parent_source)
|
220
|
+
dup.tap do |c|
|
221
|
+
c.instance_eval do
|
222
|
+
@path_resolutions = @path_resolutions.child(parent_source)
|
223
|
+
@http_resolutions = @http_resolutions.child(parent_source)
|
224
|
+
@https_resolutions = @https_resolutions.child(parent_source)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
class Default < Standard
|
231
|
+
def initialize(
|
232
|
+
path_reader: ReadPathSources,
|
233
|
+
http_reader: ReadHttpSources,
|
234
|
+
https_reader: http_reader,
|
235
|
+
ipfs_public_gateway: "cloudflare-ipfs.com"
|
236
|
+
)
|
237
|
+
super(
|
238
|
+
path_reader: ReadPathAndIPFSSources.new(
|
239
|
+
path_reader: path_reader,
|
240
|
+
http_reader: http_reader,
|
241
|
+
https_reader: https_reader,
|
242
|
+
public_gateway: ipfs_public_gateway
|
243
|
+
),
|
244
|
+
http_reader: http_reader,
|
245
|
+
https_reader: https_reader
|
246
|
+
)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
class LocalOnly < Standard
|
251
|
+
def initialize(path_reader: ReadPathSources)
|
252
|
+
super(
|
253
|
+
path_reader: path_reader,
|
254
|
+
http_reader: RejectSources,
|
255
|
+
https_reader: RejectSources
|
256
|
+
)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
class None < Default
|
261
|
+
def initialize
|
262
|
+
super(
|
263
|
+
path_reader: RejectSources,
|
264
|
+
http_reader: RejectSources,
|
265
|
+
https_reader: RejectSources
|
266
|
+
)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class ExpressionResolver
|
272
|
+
@@registry = {}
|
273
|
+
|
274
|
+
def self.for(expr)
|
275
|
+
@@registry.find { |k, _| k === expr }.last.new(expr)
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.register_for(kase)
|
279
|
+
@@registry[kase] = self
|
280
|
+
end
|
281
|
+
|
282
|
+
def initialize(expr)
|
283
|
+
@expr = expr
|
284
|
+
end
|
285
|
+
|
286
|
+
def resolve(**kwargs)
|
287
|
+
Util.promise_all_hash(
|
288
|
+
@expr.to_h.each_with_object({}) { |(attr, value), h|
|
289
|
+
h[attr] = ExpressionResolver.for(value).resolve(**kwargs)
|
290
|
+
}
|
291
|
+
).then { |h| @expr.with(h) }
|
292
|
+
end
|
293
|
+
|
294
|
+
class ImportResolver < ExpressionResolver
|
295
|
+
register_for Import
|
296
|
+
|
297
|
+
def resolve(resolver:, relative_to:)
|
298
|
+
Promise.resolve(nil).then do
|
299
|
+
resolver.cache_fetch(@expr.cache_key(relative_to)) do
|
300
|
+
resolve_raw(resolver: resolver, relative_to: relative_to)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def resolve_raw(resolver:, relative_to:)
|
306
|
+
real_path = @expr.real_path(relative_to)
|
307
|
+
real_path.resolve(resolver).then do |result|
|
308
|
+
@expr.parse_and_check(result).resolve(
|
309
|
+
resolver: resolver.child(real_path),
|
310
|
+
relative_to: real_path
|
311
|
+
)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
class FallbackResolver < ExpressionResolver
|
317
|
+
register_for Operator::ImportFallback
|
318
|
+
|
319
|
+
def resolve(**kwargs)
|
320
|
+
ExpressionResolver.for(@expr.lhs).resolve(**kwargs).catch do
|
321
|
+
ExpressionResolver.for(@expr.rhs).resolve(**kwargs)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
class ArrayResolver < ExpressionResolver
|
327
|
+
register_for Util::ArrayOf.new(Expression)
|
328
|
+
|
329
|
+
def resolve(**kwargs)
|
330
|
+
Promise.all(
|
331
|
+
@expr.map { |e| ExpressionResolver.for(e).resolve(**kwargs) }
|
332
|
+
)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
class HashResolver < ExpressionResolver
|
337
|
+
register_for Util::HashOf.new(
|
338
|
+
ValueSemantics::Anything,
|
339
|
+
ValueSemantics::Either.new([Expression, nil])
|
340
|
+
)
|
341
|
+
|
342
|
+
def resolve(**kwargs)
|
343
|
+
Util.promise_all_hash(Hash[@expr.map do |k, v|
|
344
|
+
[k, ExpressionResolver.for(v).resolve(**kwargs)]
|
345
|
+
end])
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
class RecordResolver < ExpressionResolver
|
350
|
+
register_for Record
|
351
|
+
|
352
|
+
def resolve(**kwargs)
|
353
|
+
ExpressionResolver.for(@expr.record).resolve(**kwargs).then do |h|
|
354
|
+
@expr.with(record: h)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
register_for Expression
|
360
|
+
|
361
|
+
class IdentityResolver < ExpressionResolver
|
362
|
+
register_for Object
|
363
|
+
|
364
|
+
def resolve(*)
|
365
|
+
Promise.resolve(@expr)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
class Expression
|
371
|
+
def resolve(
|
372
|
+
resolver: Resolvers::Default.new,
|
373
|
+
relative_to: Import::Path.from_string(Pathname.pwd + "file")
|
374
|
+
)
|
375
|
+
p = ExpressionResolver.for(self).resolve(
|
376
|
+
resolver: resolver,
|
377
|
+
relative_to: relative_to
|
378
|
+
)
|
379
|
+
resolver.finish!
|
380
|
+
p
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
@@ -0,0 +1,1209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dhall/ast"
|
4
|
+
require "dhall/normalize"
|
5
|
+
|
6
|
+
module Dhall
|
7
|
+
module TypeChecker
|
8
|
+
def self.assert(type, assertion, message)
|
9
|
+
raise TypeError, message unless assertion === type
|
10
|
+
type
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.assert_type(expr, assertion, message, context:)
|
14
|
+
aexpr = self.for(expr).annotate(context)
|
15
|
+
type = aexpr.type
|
16
|
+
raise TypeError, "#{message}: #{type}" unless assertion === type
|
17
|
+
aexpr
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.assert_types_match(a, b, message, context:)
|
21
|
+
atype = self.for(a).annotate(context).type
|
22
|
+
btype = self.for(b).annotate(context).type
|
23
|
+
raise TypeError, "#{message}: #{atype}, #{btype}" unless atype == btype
|
24
|
+
atype
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.for(expr)
|
28
|
+
@typecheckers.each do |node_matcher, (typechecker, extras)|
|
29
|
+
if node_matcher === expr
|
30
|
+
msg = [:call, :for, :new].find { |m| typechecker.respond_to?(m) }
|
31
|
+
return typechecker.public_send(msg, expr, *extras)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
raise TypeError, "Unknown expression: #{expr.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.register(typechecker, node_type, *extras)
|
39
|
+
@typecheckers ||= {}
|
40
|
+
@typecheckers[node_type] ||= [typechecker, extras]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.type_of(expr)
|
44
|
+
return if expr.nil?
|
45
|
+
TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
|
46
|
+
end
|
47
|
+
|
48
|
+
class Context
|
49
|
+
def initialize(bindings=Hash.new([]))
|
50
|
+
@bindings = bindings.freeze
|
51
|
+
freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch(var)
|
55
|
+
@bindings[var.name][var.index] ||
|
56
|
+
(raise TypeError, "Free variable: #{var}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def add(ftype)
|
60
|
+
self.class.new(@bindings.merge(
|
61
|
+
ftype.var => [ftype.type] + @bindings[ftype.var]
|
62
|
+
)).shift(1, ftype.var, 0)
|
63
|
+
end
|
64
|
+
|
65
|
+
def shift(amount, name, min_index)
|
66
|
+
self.class.new(@bindings.merge(
|
67
|
+
Hash[@bindings.map do |var, bindings|
|
68
|
+
[var, bindings.map { |b| b.shift(amount, name, min_index) }]
|
69
|
+
end]
|
70
|
+
))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
KINDS = [
|
75
|
+
Dhall::Variable["Type"],
|
76
|
+
Dhall::Variable["Kind"],
|
77
|
+
Dhall::Variable["Sort"]
|
78
|
+
].freeze
|
79
|
+
|
80
|
+
class Variable
|
81
|
+
TypeChecker.register self, Dhall::Variable
|
82
|
+
|
83
|
+
def initialize(var)
|
84
|
+
@var = var
|
85
|
+
end
|
86
|
+
|
87
|
+
BUILTIN = {
|
88
|
+
"Type" => Dhall::Variable["Kind"],
|
89
|
+
"Kind" => Dhall::Variable["Sort"],
|
90
|
+
"Bool" => Dhall::Variable["Type"],
|
91
|
+
"Natural" => Dhall::Variable["Type"],
|
92
|
+
"Integer" => Dhall::Variable["Type"],
|
93
|
+
"Double" => Dhall::Variable["Type"],
|
94
|
+
"Text" => Dhall::Variable["Type"],
|
95
|
+
"List" => Dhall::Forall.of_arguments(
|
96
|
+
Dhall::Variable["Type"],
|
97
|
+
body: Dhall::Variable["Type"]
|
98
|
+
),
|
99
|
+
"Optional" => Dhall::Forall.of_arguments(
|
100
|
+
Dhall::Variable["Type"],
|
101
|
+
body: Dhall::Variable["Type"]
|
102
|
+
),
|
103
|
+
"None" => Dhall::Forall.new(
|
104
|
+
var: "A",
|
105
|
+
type: Dhall::Variable["Type"],
|
106
|
+
body: Dhall::Application.new(
|
107
|
+
function: Dhall::Variable["Optional"],
|
108
|
+
argument: Dhall::Variable["A"]
|
109
|
+
)
|
110
|
+
)
|
111
|
+
}.freeze
|
112
|
+
|
113
|
+
def annotate(context)
|
114
|
+
raise TypeError, "Sort has no Type, Kind, or Sort" if @var.name == "Sort"
|
115
|
+
|
116
|
+
Dhall::TypeAnnotation.new(
|
117
|
+
value: @var,
|
118
|
+
type: BUILTIN.fetch(@var.name) { context.fetch(@var) }
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Literal
|
124
|
+
TypeChecker.register self, Dhall::Bool
|
125
|
+
TypeChecker.register self, Dhall::Natural
|
126
|
+
TypeChecker.register self, Dhall::Text
|
127
|
+
TypeChecker.register self, Dhall::Integer
|
128
|
+
TypeChecker.register self, Dhall::Double
|
129
|
+
|
130
|
+
def initialize(lit)
|
131
|
+
@lit = lit
|
132
|
+
@type = Dhall::Variable[lit.class.name.split(/::/).last]
|
133
|
+
end
|
134
|
+
|
135
|
+
def annotate(*)
|
136
|
+
Dhall::TypeAnnotation.new(
|
137
|
+
value: @lit,
|
138
|
+
type: @type
|
139
|
+
)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class TextLiteral
|
144
|
+
TypeChecker.register self, Dhall::TextLiteral
|
145
|
+
|
146
|
+
def initialize(lit)
|
147
|
+
@lit = lit
|
148
|
+
end
|
149
|
+
|
150
|
+
class Chunks
|
151
|
+
def initialize(chunks)
|
152
|
+
@chunks = chunks
|
153
|
+
end
|
154
|
+
|
155
|
+
def map
|
156
|
+
self.class.new(@chunks.map { |c|
|
157
|
+
if c.is_a?(Dhall::Text)
|
158
|
+
c
|
159
|
+
else
|
160
|
+
yield c
|
161
|
+
end
|
162
|
+
})
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_a
|
166
|
+
@chunks
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def annotate(context)
|
171
|
+
chunks = Chunks.new(@lit.chunks).map { |c|
|
172
|
+
TypeChecker.for(c).annotate(context).tap do |annotated|
|
173
|
+
TypeChecker.assert annotated.type, Dhall::Variable["Text"],
|
174
|
+
"Cannot interpolate #{annotated.type}"
|
175
|
+
end
|
176
|
+
}.to_a
|
177
|
+
|
178
|
+
Dhall::TypeAnnotation.new(
|
179
|
+
value: @lit.with(chunks: chunks),
|
180
|
+
type: Dhall::Variable["Text"]
|
181
|
+
)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class If
|
186
|
+
TypeChecker.register self, Dhall::If
|
187
|
+
|
188
|
+
def initialize(expr)
|
189
|
+
@expr = expr
|
190
|
+
@predicate = TypeChecker.for(expr.predicate)
|
191
|
+
@then = TypeChecker.for(expr.then)
|
192
|
+
@else = TypeChecker.for(expr.else)
|
193
|
+
end
|
194
|
+
|
195
|
+
class AnnotatedIf
|
196
|
+
def initialize(expr, apred, athen, aelse, context:)
|
197
|
+
TypeChecker.assert apred.type, Dhall::Variable["Bool"],
|
198
|
+
"If must have a predicate of type Bool"
|
199
|
+
TypeChecker.assert_type athen.type, Dhall::Variable["Type"],
|
200
|
+
"If branches must have types of type Type",
|
201
|
+
context: context
|
202
|
+
TypeChecker.assert aelse.type, athen.type,
|
203
|
+
"If branches have mismatched types"
|
204
|
+
@expr = expr.with(predicate: apred, then: athen, else: aelse)
|
205
|
+
end
|
206
|
+
|
207
|
+
def annotation
|
208
|
+
Dhall::TypeAnnotation.new(
|
209
|
+
value: @expr,
|
210
|
+
type: @expr.then.type
|
211
|
+
)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def annotate(context)
|
216
|
+
AnnotatedIf.new(
|
217
|
+
@expr,
|
218
|
+
@predicate.annotate(context),
|
219
|
+
@then.annotate(context),
|
220
|
+
@else.annotate(context),
|
221
|
+
context: context
|
222
|
+
).annotation
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
class Operator
|
227
|
+
{
|
228
|
+
Dhall::Operator::And => Dhall::Variable["Bool"],
|
229
|
+
Dhall::Operator::Or => Dhall::Variable["Bool"],
|
230
|
+
Dhall::Operator::Equal => Dhall::Variable["Bool"],
|
231
|
+
Dhall::Operator::NotEqual => Dhall::Variable["Bool"],
|
232
|
+
Dhall::Operator::Plus => Dhall::Variable["Natural"],
|
233
|
+
Dhall::Operator::Times => Dhall::Variable["Natural"],
|
234
|
+
Dhall::Operator::TextConcatenate => Dhall::Variable["Text"]
|
235
|
+
}.each do |node_type, type|
|
236
|
+
TypeChecker.register self, node_type, type
|
237
|
+
end
|
238
|
+
|
239
|
+
def initialize(expr, type)
|
240
|
+
@expr = expr
|
241
|
+
@lhs = TypeChecker.for(expr.lhs)
|
242
|
+
@rhs = TypeChecker.for(expr.rhs)
|
243
|
+
@type = type
|
244
|
+
end
|
245
|
+
|
246
|
+
def annotate(context)
|
247
|
+
annotated_lhs = @lhs.annotate(context)
|
248
|
+
annotated_rhs = @rhs.annotate(context)
|
249
|
+
types = [annotated_lhs.type, annotated_rhs.type]
|
250
|
+
if types.any? { |t| t != @type }
|
251
|
+
raise TypeError, "Operator arguments not #{@type}: #{types}"
|
252
|
+
end
|
253
|
+
|
254
|
+
Dhall::TypeAnnotation.new(
|
255
|
+
value: @expr.with(lhs: annotated_lhs, rhs: annotated_rhs),
|
256
|
+
type: @type
|
257
|
+
)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class OperatorListConcatenate
|
262
|
+
TypeChecker.register self, Dhall::Operator::ListConcatenate
|
263
|
+
|
264
|
+
def initialize(expr)
|
265
|
+
@expr = expr
|
266
|
+
@lhs = TypeChecker.for(expr.lhs)
|
267
|
+
@rhs = TypeChecker.for(expr.rhs)
|
268
|
+
end
|
269
|
+
|
270
|
+
module IsList
|
271
|
+
def self.===(other)
|
272
|
+
other.is_a?(Dhall::Application) &&
|
273
|
+
other.function == Dhall::Variable["List"]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def annotate(context)
|
278
|
+
annotated_lhs = @lhs.annotate(context)
|
279
|
+
annotated_rhs = @rhs.annotate(context)
|
280
|
+
|
281
|
+
types = [annotated_lhs.type, annotated_rhs.type]
|
282
|
+
assertion = Util::ArrayOf.new(Util::AllOf.new(IsList, types.first))
|
283
|
+
TypeChecker.assert types, assertion,
|
284
|
+
"Operator arguments wrong: #{types}"
|
285
|
+
|
286
|
+
Dhall::TypeAnnotation.new(
|
287
|
+
value: @expr.with(lhs: annotated_lhs, rhs: annotated_rhs),
|
288
|
+
type: types.first
|
289
|
+
)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class OperatorRecursiveRecordMerge
|
294
|
+
TypeChecker.register self, Dhall::Operator::RecursiveRecordMerge
|
295
|
+
|
296
|
+
def initialize(expr)
|
297
|
+
@expr = expr
|
298
|
+
@lhs = TypeChecker.for(expr.lhs)
|
299
|
+
@rhs = TypeChecker.for(expr.rhs)
|
300
|
+
end
|
301
|
+
|
302
|
+
def annotate(context)
|
303
|
+
annotated_lhs = @lhs.annotate(context)
|
304
|
+
annotated_rhs = @rhs.annotate(context)
|
305
|
+
|
306
|
+
type = annotated_lhs.type.deep_merge_type(annotated_rhs.type)
|
307
|
+
|
308
|
+
TypeChecker.assert type, Dhall::RecordType,
|
309
|
+
"RecursiveRecordMerge got #{type}"
|
310
|
+
|
311
|
+
# Annotate to sanity check
|
312
|
+
TypeChecker.for(type).annotate(context)
|
313
|
+
|
314
|
+
Dhall::TypeAnnotation.new(
|
315
|
+
value: @expr.with(lhs: annotated_lhs, rhs: annotated_rhs),
|
316
|
+
type: type
|
317
|
+
)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
class OperatorRightBiasedRecordMerge
|
322
|
+
TypeChecker.register self, Dhall::Operator::RightBiasedRecordMerge
|
323
|
+
|
324
|
+
def initialize(expr)
|
325
|
+
@expr = expr
|
326
|
+
end
|
327
|
+
|
328
|
+
def check(context)
|
329
|
+
annotated_lhs = TypeChecker.assert_type @expr.lhs, Dhall::RecordType,
|
330
|
+
"RecursiveRecordMerge got",
|
331
|
+
context: context
|
332
|
+
|
333
|
+
annotated_rhs = TypeChecker.assert_type @expr.rhs, Dhall::RecordType,
|
334
|
+
"RecursiveRecordMerge got",
|
335
|
+
context: context
|
336
|
+
|
337
|
+
TypeChecker.assert_types_match annotated_lhs.type, annotated_rhs.type,
|
338
|
+
"RecursiveRecordMerge got mixed kinds",
|
339
|
+
context: context
|
340
|
+
|
341
|
+
[annotated_lhs, annotated_rhs]
|
342
|
+
end
|
343
|
+
|
344
|
+
def annotate(context)
|
345
|
+
annotated_lhs, annotated_rhs = check(context)
|
346
|
+
|
347
|
+
Dhall::TypeAnnotation.new(
|
348
|
+
value: @expr.with(lhs: annotated_lhs, rhs: annotated_rhs),
|
349
|
+
type: TypeChecker.for(
|
350
|
+
annotated_lhs.type.merge_type(annotated_rhs.type)
|
351
|
+
).annotate(context).value
|
352
|
+
)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
class OperatorRecursiveRecordTypeMerge
|
357
|
+
TypeChecker.register self, Dhall::Operator::RecursiveRecordTypeMerge
|
358
|
+
|
359
|
+
def initialize(expr)
|
360
|
+
@expr = expr
|
361
|
+
end
|
362
|
+
|
363
|
+
def annotate(context)
|
364
|
+
kind = TypeChecker.assert_types_match(
|
365
|
+
@expr.lhs, @expr.rhs,
|
366
|
+
"RecursiveRecordTypeMerge mixed kinds",
|
367
|
+
context: context
|
368
|
+
)
|
369
|
+
|
370
|
+
type = @expr.lhs.deep_merge_type(@expr.rhs)
|
371
|
+
|
372
|
+
TypeChecker.assert type, Dhall::RecordType,
|
373
|
+
"RecursiveRecordMerge got #{type}"
|
374
|
+
|
375
|
+
# Annotate to sanity check
|
376
|
+
TypeChecker.for(type).annotate(context)
|
377
|
+
|
378
|
+
Dhall::TypeAnnotation.new(value: @expr, type: kind)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
class EmptyList
|
383
|
+
TypeChecker.register self, Dhall::EmptyList
|
384
|
+
|
385
|
+
def initialize(expr)
|
386
|
+
@expr = expr
|
387
|
+
end
|
388
|
+
|
389
|
+
def annotate(context)
|
390
|
+
TypeChecker.assert_type @expr.element_type, Dhall::Variable["Type"],
|
391
|
+
"EmptyList element type not of type Type",
|
392
|
+
context: context
|
393
|
+
|
394
|
+
Dhall::TypeAnnotation.new(type: @expr.type, value: @expr)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
class List
|
399
|
+
TypeChecker.register self, Dhall::List
|
400
|
+
|
401
|
+
def initialize(list)
|
402
|
+
@list = list
|
403
|
+
end
|
404
|
+
|
405
|
+
class AnnotatedList
|
406
|
+
def initialize(alist)
|
407
|
+
@alist = alist
|
408
|
+
end
|
409
|
+
|
410
|
+
def annotation
|
411
|
+
list = @alist.with(element_type: element_type)
|
412
|
+
Dhall::TypeAnnotation.new(type: list.type, value: list)
|
413
|
+
end
|
414
|
+
|
415
|
+
def element_type
|
416
|
+
@alist.first.value&.type || @alist.element_type
|
417
|
+
end
|
418
|
+
|
419
|
+
def element_types
|
420
|
+
@alist.to_a.map(&:type)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def annotate(context)
|
425
|
+
alist = AnnotatedList.new(@list.map(type: @list.element_type) { |el|
|
426
|
+
TypeChecker.for(el).annotate(context)
|
427
|
+
})
|
428
|
+
|
429
|
+
TypeChecker.assert alist.element_types,
|
430
|
+
Util::ArrayOf.new(alist.element_type),
|
431
|
+
"Non-homogenous List"
|
432
|
+
|
433
|
+
TypeChecker.assert_type alist.element_type, Dhall::Variable["Type"],
|
434
|
+
"List type not of type Type", context: context
|
435
|
+
|
436
|
+
alist.annotation
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
class OptionalNone
|
441
|
+
TypeChecker.register self, Dhall::OptionalNone
|
442
|
+
|
443
|
+
def initialize(expr)
|
444
|
+
@expr = expr
|
445
|
+
end
|
446
|
+
|
447
|
+
def annotate(context)
|
448
|
+
TypeChecker.assert(
|
449
|
+
TypeChecker.for(@expr.value_type).annotate(context).type,
|
450
|
+
Dhall::Variable["Type"],
|
451
|
+
"OptionalNone element type not of type Type"
|
452
|
+
)
|
453
|
+
|
454
|
+
Dhall::TypeAnnotation.new(type: @expr.type, value: @expr)
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
class Optional
|
459
|
+
TypeChecker.register self, Dhall::Optional
|
460
|
+
|
461
|
+
def initialize(some)
|
462
|
+
@some = some
|
463
|
+
end
|
464
|
+
|
465
|
+
def annotate(context)
|
466
|
+
asome = @some.map do |el|
|
467
|
+
TypeChecker.for(el).annotate(context)
|
468
|
+
end
|
469
|
+
some = asome.with(value_type: asome.value.type)
|
470
|
+
|
471
|
+
type_type = TypeChecker.for(some.value_type).annotate(context).type
|
472
|
+
TypeChecker.assert type_type, Dhall::Variable["Type"],
|
473
|
+
"Some type not of type Type, was: #{type_type}"
|
474
|
+
|
475
|
+
Dhall::TypeAnnotation.new(type: some.type, value: some)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
class EmptyAnonymousType
|
480
|
+
TypeChecker.register self, Dhall::EmptyRecordType
|
481
|
+
|
482
|
+
def initialize(expr)
|
483
|
+
@expr = expr
|
484
|
+
end
|
485
|
+
|
486
|
+
def annotate(*)
|
487
|
+
Dhall::TypeAnnotation.new(
|
488
|
+
value: @expr,
|
489
|
+
type: Dhall::Variable["Type"]
|
490
|
+
)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
class AnonymousType
|
495
|
+
TypeChecker.register self, Dhall::RecordType
|
496
|
+
TypeChecker.register self, Dhall::UnionType
|
497
|
+
|
498
|
+
def initialize(type)
|
499
|
+
@type = type
|
500
|
+
end
|
501
|
+
|
502
|
+
def annotate(context)
|
503
|
+
kinds = @type.record.values.compact.map do |mtype|
|
504
|
+
TypeChecker.for(mtype).annotate(context).type
|
505
|
+
end
|
506
|
+
|
507
|
+
TypeChecker.assert (kinds - KINDS), [],
|
508
|
+
"AnonymousType field kind not one of #{KINDS}"
|
509
|
+
|
510
|
+
TypeChecker.assert kinds, Util::ArrayAllTheSame,
|
511
|
+
"AnonymousType field kinds not all the same"
|
512
|
+
|
513
|
+
type = kinds.first || KINDS.first
|
514
|
+
Dhall::TypeAnnotation.new(value: @type, type: type)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
class EmptyRecord
|
519
|
+
TypeChecker.register self, Dhall::EmptyRecord
|
520
|
+
|
521
|
+
def initialize(expr)
|
522
|
+
@expr = expr
|
523
|
+
end
|
524
|
+
|
525
|
+
def annotate(*)
|
526
|
+
Dhall::TypeAnnotation.new(
|
527
|
+
value: @expr,
|
528
|
+
type: Dhall::EmptyRecordType.new
|
529
|
+
)
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
class Record
|
534
|
+
TypeChecker.register self, Dhall::Record
|
535
|
+
|
536
|
+
def initialize(record)
|
537
|
+
@record = record
|
538
|
+
end
|
539
|
+
|
540
|
+
def annotate(context)
|
541
|
+
arecord = @record.map do |k, v|
|
542
|
+
[k, TypeChecker.for(v).annotate(context)]
|
543
|
+
end
|
544
|
+
|
545
|
+
Dhall::TypeAnnotation.new(
|
546
|
+
value: arecord,
|
547
|
+
type: TypeChecker.for(Dhall::RecordType.for(Hash[
|
548
|
+
arecord.record.map { |k, v| [k, v.type] }
|
549
|
+
])).annotate(context).value
|
550
|
+
)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
class RecordSelection
|
555
|
+
TypeChecker.register self, Dhall::RecordSelection
|
556
|
+
|
557
|
+
def initialize(selection)
|
558
|
+
@selection = selection
|
559
|
+
@record = selection.record
|
560
|
+
@selector = selection.selector
|
561
|
+
end
|
562
|
+
|
563
|
+
class Selector
|
564
|
+
def self.for(annotated_record)
|
565
|
+
if annotated_record.type == Dhall::Variable["Type"]
|
566
|
+
TypeSelector.new(annotated_record.value)
|
567
|
+
elsif annotated_record.type.class == Dhall::RecordType
|
568
|
+
new(annotated_record.type)
|
569
|
+
else
|
570
|
+
raise TypeError, "RecordSelection on #{annotated_record.type}"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
def initialize(type)
|
575
|
+
@fetch_from = type.record
|
576
|
+
end
|
577
|
+
|
578
|
+
def select(selector)
|
579
|
+
@fetch_from.fetch(selector) do
|
580
|
+
raise TypeError, "#{@fetch_from} has no field #{@selector}"
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
class TypeSelector < Selector
|
586
|
+
def initialize(union)
|
587
|
+
normalized = union.normalize
|
588
|
+
TypeChecker.assert normalized, Dhall::UnionType,
|
589
|
+
"RecordSelection on #{normalized}"
|
590
|
+
@fetch_from = normalized.constructor_types
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
def annotate(context)
|
595
|
+
arecord = TypeChecker.for(@record).annotate(context)
|
596
|
+
selector = Selector.for(arecord)
|
597
|
+
|
598
|
+
Dhall::TypeAnnotation.new(
|
599
|
+
value: @selection.with(record: arecord),
|
600
|
+
type: selector.select(@selector)
|
601
|
+
)
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
class RecordProjection
|
606
|
+
TypeChecker.register self, Dhall::EmptyRecordProjection
|
607
|
+
TypeChecker.register self, Dhall::RecordProjection
|
608
|
+
|
609
|
+
def initialize(projection)
|
610
|
+
@projection = projection
|
611
|
+
@record = TypeChecker.for(projection.record)
|
612
|
+
@selectors = projection.selectors
|
613
|
+
end
|
614
|
+
|
615
|
+
def annotate(context)
|
616
|
+
arecord = @record.annotate(context)
|
617
|
+
|
618
|
+
TypeChecker.assert arecord.type.class.name, "Dhall::RecordType",
|
619
|
+
"RecordProjection on #{arecord.type}"
|
620
|
+
|
621
|
+
slice = arecord.type.slice(@selectors)
|
622
|
+
TypeChecker.assert slice.keys, @selectors,
|
623
|
+
"#{arecord.type} missing one of: #{@selectors}"
|
624
|
+
|
625
|
+
Dhall::TypeAnnotation.new(
|
626
|
+
value: @projection.with(record: arecord),
|
627
|
+
type: slice
|
628
|
+
)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
class Union
|
633
|
+
TypeChecker.register self, Dhall::Union
|
634
|
+
|
635
|
+
def initialize(union)
|
636
|
+
@union = union
|
637
|
+
@value = TypeChecker.for(union.value)
|
638
|
+
end
|
639
|
+
|
640
|
+
def annotate(context)
|
641
|
+
annotated_value = @value.annotate(context)
|
642
|
+
|
643
|
+
type = Dhall::UnionType.new(
|
644
|
+
alternatives: { @union.tag => annotated_value.type }
|
645
|
+
).merge(@union.alternatives)
|
646
|
+
|
647
|
+
# Annotate to sanity check
|
648
|
+
TypeChecker.for(type).annotate(context)
|
649
|
+
|
650
|
+
Dhall::TypeAnnotation.new(
|
651
|
+
value: @union.with(value: annotated_value),
|
652
|
+
type: type
|
653
|
+
)
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
class Merge
|
658
|
+
TypeChecker.register self, Dhall::Merge
|
659
|
+
|
660
|
+
def initialize(merge)
|
661
|
+
@merge = merge
|
662
|
+
@record = TypeChecker.for(merge.record)
|
663
|
+
@union = TypeChecker.for(merge.input)
|
664
|
+
end
|
665
|
+
|
666
|
+
class Handlers
|
667
|
+
def initialize(annotation)
|
668
|
+
@type = annotation.type
|
669
|
+
|
670
|
+
TypeChecker.assert @type, Dhall::RecordType,
|
671
|
+
"Merge expected Record got: #{@type}"
|
672
|
+
end
|
673
|
+
|
674
|
+
def output_type(output_annotation=nil)
|
675
|
+
@type.record.values.reduce(output_annotation) do |type_acc, htype|
|
676
|
+
htype = htype.body.shift(-1, htype.var, 0) if htype.is_a?(Dhall::Forall)
|
677
|
+
|
678
|
+
if type_acc && htype.normalize != type_acc.normalize
|
679
|
+
raise TypeError, "Handler output types must all match"
|
680
|
+
end
|
681
|
+
|
682
|
+
htype
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
def keys
|
687
|
+
@type.record.keys
|
688
|
+
end
|
689
|
+
|
690
|
+
def fetch_input_type(k)
|
691
|
+
type = @type.record.fetch(k) do
|
692
|
+
raise TypeError, "No merge handler for alternative: #{k}"
|
693
|
+
end
|
694
|
+
|
695
|
+
TypeChecker.assert type, Dhall::Forall, "Handler is not a function"
|
696
|
+
|
697
|
+
type.type
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
class AnnotatedMerge
|
702
|
+
def initialize(merge:, record:, input:)
|
703
|
+
@merge = merge.with(record: record, input: input)
|
704
|
+
@handlers = Handlers.new(record)
|
705
|
+
@record = record
|
706
|
+
@union = input
|
707
|
+
|
708
|
+
TypeChecker.assert @union.type, Dhall::UnionType,
|
709
|
+
"Merge expected Union got: #{@union.type}"
|
710
|
+
|
711
|
+
assert_union_and_handlers_match
|
712
|
+
end
|
713
|
+
|
714
|
+
def annotation
|
715
|
+
Dhall::TypeAnnotation.new(
|
716
|
+
value: @merge,
|
717
|
+
type: type
|
718
|
+
)
|
719
|
+
end
|
720
|
+
|
721
|
+
def type
|
722
|
+
@type ||= @handlers.output_type(@merge.type)
|
723
|
+
end
|
724
|
+
|
725
|
+
def assert_kind(context)
|
726
|
+
kind = TypeChecker.for(type).annotate(context).type
|
727
|
+
|
728
|
+
TypeChecker.assert(
|
729
|
+
kind,
|
730
|
+
Dhall::Variable["Type"],
|
731
|
+
"Merge must have kind Type"
|
732
|
+
)
|
733
|
+
|
734
|
+
kind
|
735
|
+
end
|
736
|
+
|
737
|
+
def assert_union_and_handlers_match
|
738
|
+
extras = @handlers.keys - @union.type.alternatives.keys
|
739
|
+
TypeChecker.assert extras, [],
|
740
|
+
"Merge handlers unknown alternatives: #{extras}"
|
741
|
+
|
742
|
+
@union.type.alternatives.each do |k, atype|
|
743
|
+
atype.nil? || TypeChecker.assert(
|
744
|
+
@handlers.fetch_input_type(k),
|
745
|
+
atype,
|
746
|
+
"Handler argument does not match alternative type: #{atype}"
|
747
|
+
)
|
748
|
+
end
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
def annotate(context)
|
753
|
+
amerge = AnnotatedMerge.new(
|
754
|
+
merge: @merge,
|
755
|
+
record: @record.annotate(context),
|
756
|
+
input: @union.annotate(context)
|
757
|
+
)
|
758
|
+
amerge.assert_kind(context)
|
759
|
+
amerge.annotation
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
class Forall
|
764
|
+
TypeChecker.register self, Dhall::Forall
|
765
|
+
|
766
|
+
def initialize(expr)
|
767
|
+
@expr = expr
|
768
|
+
@var = expr.var
|
769
|
+
@var_type = expr.type
|
770
|
+
@input = TypeChecker.for(expr.type)
|
771
|
+
@output = TypeChecker.for(expr.body)
|
772
|
+
end
|
773
|
+
|
774
|
+
module FunctionKind
|
775
|
+
def self.for(inkind, outkind)
|
776
|
+
if inkind.nil? || outkind.nil?
|
777
|
+
raise TypeError, "FunctionType part of this is a term"
|
778
|
+
end
|
779
|
+
|
780
|
+
raise TypeError, "Dependent types are not allowed" if outkind > inkind
|
781
|
+
|
782
|
+
if outkind.zero?
|
783
|
+
Term.new
|
784
|
+
else
|
785
|
+
Polymorphic.new(inkind, outkind)
|
786
|
+
end
|
787
|
+
end
|
788
|
+
|
789
|
+
class Term
|
790
|
+
def kind
|
791
|
+
KINDS.first
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
class Polymorphic
|
796
|
+
def initialize(inkind, outkind)
|
797
|
+
@inkind = inkind
|
798
|
+
@outkind = outkind
|
799
|
+
end
|
800
|
+
|
801
|
+
def kind
|
802
|
+
KINDS[[@outkind, @inkind].max]
|
803
|
+
end
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
def annotate(context)
|
808
|
+
inkind = KINDS.index(@input.annotate(context).type)
|
809
|
+
outkind = KINDS.index(@output.annotate(context.add(@expr)).type)
|
810
|
+
|
811
|
+
Dhall::TypeAnnotation.new(
|
812
|
+
value: @expr,
|
813
|
+
type: FunctionKind.for(inkind, outkind).kind
|
814
|
+
)
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
class Function
|
819
|
+
TypeChecker.register self, Dhall::Function
|
820
|
+
|
821
|
+
def initialize(func)
|
822
|
+
@func = func
|
823
|
+
@type = Dhall::Forall.new(
|
824
|
+
var: func.var,
|
825
|
+
type: func.type,
|
826
|
+
body: Dhall::Variable["UNKNOWN"]
|
827
|
+
)
|
828
|
+
@output = TypeChecker.for(func.body)
|
829
|
+
end
|
830
|
+
|
831
|
+
def annotate(context)
|
832
|
+
abody = @output.annotate(context.add(@type))
|
833
|
+
|
834
|
+
Dhall::TypeAnnotation.new(
|
835
|
+
value: @func.with(body: abody),
|
836
|
+
type: TypeChecker.for(
|
837
|
+
@type.with(body: abody.type)
|
838
|
+
).annotate(context).value
|
839
|
+
)
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
class Application
|
844
|
+
TypeChecker.register self, Dhall::Application
|
845
|
+
|
846
|
+
def initialize(app)
|
847
|
+
@app = app
|
848
|
+
@func = TypeChecker.for(app.function)
|
849
|
+
@arg = app.argument
|
850
|
+
end
|
851
|
+
|
852
|
+
def annotate(context)
|
853
|
+
afunc = @func.annotate(context)
|
854
|
+
|
855
|
+
TypeChecker.assert afunc.type, Dhall::Forall,
|
856
|
+
"Application LHS is not a function"
|
857
|
+
|
858
|
+
aarg = TypeChecker.for(
|
859
|
+
Dhall::TypeAnnotation.new(value: @arg, type: afunc.type.type)
|
860
|
+
).annotate(context)
|
861
|
+
|
862
|
+
Dhall::TypeAnnotation.new(
|
863
|
+
value: @app.with(function: afunc, argument: aarg),
|
864
|
+
type: afunc.type.call(aarg.value)
|
865
|
+
)
|
866
|
+
end
|
867
|
+
end
|
868
|
+
|
869
|
+
TypeChecker.register ->(blk) { LetIn.for(blk.unflatten) }, Dhall::LetBlock
|
870
|
+
|
871
|
+
class LetIn
|
872
|
+
TypeChecker.register self, Dhall::LetIn
|
873
|
+
|
874
|
+
def self.for(letin)
|
875
|
+
if letin.let.type
|
876
|
+
LetInAnnotated.new(letin)
|
877
|
+
else
|
878
|
+
LetIn.new(letin)
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
882
|
+
def initialize(letin)
|
883
|
+
@letin = letin
|
884
|
+
@let = @letin.let
|
885
|
+
end
|
886
|
+
|
887
|
+
def annotate(context)
|
888
|
+
alet = @let.with(type: assign_type(context))
|
889
|
+
type = TypeChecker.for(@letin.eliminate).annotate(context).type
|
890
|
+
abody = Dhall::TypeAnnotation.new(value: @letin.body, type: type)
|
891
|
+
Dhall::TypeAnnotation.new(
|
892
|
+
value: @letin.with(let: alet, body: abody),
|
893
|
+
type: type
|
894
|
+
)
|
895
|
+
end
|
896
|
+
|
897
|
+
protected
|
898
|
+
|
899
|
+
def assign_type(context)
|
900
|
+
TypeChecker.for(@let.assign).annotate(context).type
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
class LetInAnnotated < LetIn
|
905
|
+
protected
|
906
|
+
|
907
|
+
def assign_type(context)
|
908
|
+
TypeChecker.for(
|
909
|
+
Dhall::TypeAnnotation.new(
|
910
|
+
value: @let.assign,
|
911
|
+
type: @let.type
|
912
|
+
)
|
913
|
+
).annotate(context).type
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
class TypeAnnotation
|
918
|
+
TypeChecker.register self, Dhall::TypeAnnotation
|
919
|
+
|
920
|
+
def initialize(expr)
|
921
|
+
@expr = expr
|
922
|
+
end
|
923
|
+
|
924
|
+
def annotate(context)
|
925
|
+
redo_annotation = TypeChecker.for(@expr.value).annotate(context)
|
926
|
+
|
927
|
+
if redo_annotation.type.normalize == @expr.type.normalize
|
928
|
+
redo_annotation
|
929
|
+
else
|
930
|
+
raise TypeError, "TypeAnnotation does not match: " \
|
931
|
+
"#{@expr.type}, #{redo_annotation.type}"
|
932
|
+
end
|
933
|
+
end
|
934
|
+
end
|
935
|
+
|
936
|
+
BUILTIN_TYPES = {
|
937
|
+
"Natural/build" => Dhall::Forall.of_arguments(
|
938
|
+
Dhall::Forall.new(
|
939
|
+
var: "natural",
|
940
|
+
type: Dhall::Variable["Type"],
|
941
|
+
body: Dhall::Forall.new(
|
942
|
+
var: "succ",
|
943
|
+
type: Dhall::Forall.of_arguments(
|
944
|
+
Dhall::Variable["natural"],
|
945
|
+
body: Dhall::Variable["natural"]
|
946
|
+
),
|
947
|
+
body: Dhall::Forall.new(
|
948
|
+
var: "zero",
|
949
|
+
type: Dhall::Variable["natural"],
|
950
|
+
body: Dhall::Variable["natural"]
|
951
|
+
)
|
952
|
+
)
|
953
|
+
),
|
954
|
+
body: Dhall::Variable["Natural"]
|
955
|
+
),
|
956
|
+
"Natural/fold" => Dhall::Forall.of_arguments(
|
957
|
+
Dhall::Variable["Natural"],
|
958
|
+
body: Dhall::Forall.new(
|
959
|
+
var: "natural",
|
960
|
+
type: Dhall::Variable["Type"],
|
961
|
+
body: Dhall::Forall.new(
|
962
|
+
var: "succ",
|
963
|
+
type: Dhall::Forall.of_arguments(
|
964
|
+
Dhall::Variable["natural"],
|
965
|
+
body: Dhall::Variable["natural"]
|
966
|
+
),
|
967
|
+
body: Dhall::Forall.new(
|
968
|
+
var: "zero",
|
969
|
+
type: Dhall::Variable["natural"],
|
970
|
+
body: Dhall::Variable["natural"]
|
971
|
+
)
|
972
|
+
)
|
973
|
+
)
|
974
|
+
),
|
975
|
+
"Natural/isZero" => Dhall::Forall.of_arguments(
|
976
|
+
Dhall::Variable["Natural"],
|
977
|
+
body: Dhall::Variable["Bool"]
|
978
|
+
),
|
979
|
+
"Natural/even" => Dhall::Forall.of_arguments(
|
980
|
+
Dhall::Variable["Natural"],
|
981
|
+
body: Dhall::Variable["Bool"]
|
982
|
+
),
|
983
|
+
"Natural/odd" => Dhall::Forall.of_arguments(
|
984
|
+
Dhall::Variable["Natural"],
|
985
|
+
body: Dhall::Variable["Bool"]
|
986
|
+
),
|
987
|
+
"Natural/toInteger" => Dhall::Forall.of_arguments(
|
988
|
+
Dhall::Variable["Natural"],
|
989
|
+
body: Dhall::Variable["Integer"]
|
990
|
+
),
|
991
|
+
"Natural/show" => Dhall::Forall.of_arguments(
|
992
|
+
Dhall::Variable["Natural"],
|
993
|
+
body: Dhall::Variable["Text"]
|
994
|
+
),
|
995
|
+
"Text/show" => Dhall::Forall.of_arguments(
|
996
|
+
Dhall::Variable["Text"],
|
997
|
+
body: Dhall::Variable["Text"]
|
998
|
+
),
|
999
|
+
"List/build" => Dhall::Forall.new(
|
1000
|
+
var: "a",
|
1001
|
+
type: Dhall::Variable["Type"],
|
1002
|
+
body: Dhall::Forall.of_arguments(
|
1003
|
+
Dhall::Forall.new(
|
1004
|
+
var: "list",
|
1005
|
+
type: Dhall::Variable["Type"],
|
1006
|
+
body: Dhall::Forall.new(
|
1007
|
+
var: "cons",
|
1008
|
+
type: Dhall::Forall.of_arguments(
|
1009
|
+
Dhall::Variable["a"],
|
1010
|
+
Dhall::Variable["list"],
|
1011
|
+
body: Dhall::Variable["list"]
|
1012
|
+
),
|
1013
|
+
body: Dhall::Forall.new(
|
1014
|
+
var: "nil",
|
1015
|
+
type: Dhall::Variable["list"],
|
1016
|
+
body: Dhall::Variable["list"]
|
1017
|
+
)
|
1018
|
+
)
|
1019
|
+
),
|
1020
|
+
body: Dhall::Application.new(
|
1021
|
+
function: Dhall::Variable["List"],
|
1022
|
+
argument: Dhall::Variable["a"]
|
1023
|
+
)
|
1024
|
+
)
|
1025
|
+
),
|
1026
|
+
"List/fold" => Dhall::Forall.new(
|
1027
|
+
var: "a",
|
1028
|
+
type: Dhall::Variable["Type"],
|
1029
|
+
body: Dhall::Forall.of_arguments(
|
1030
|
+
Dhall::Application.new(
|
1031
|
+
function: Dhall::Variable["List"],
|
1032
|
+
argument: Dhall::Variable["a"]
|
1033
|
+
),
|
1034
|
+
body: Dhall::Forall.new(
|
1035
|
+
var: "list",
|
1036
|
+
type: Dhall::Variable["Type"],
|
1037
|
+
body: Dhall::Forall.new(
|
1038
|
+
var: "cons",
|
1039
|
+
type: Dhall::Forall.of_arguments(
|
1040
|
+
Dhall::Variable["a"],
|
1041
|
+
Dhall::Variable["list"],
|
1042
|
+
body: Dhall::Variable["list"]
|
1043
|
+
),
|
1044
|
+
body: Dhall::Forall.new(
|
1045
|
+
var: "nil",
|
1046
|
+
type: Dhall::Variable["list"],
|
1047
|
+
body: Dhall::Variable["list"]
|
1048
|
+
)
|
1049
|
+
)
|
1050
|
+
)
|
1051
|
+
)
|
1052
|
+
),
|
1053
|
+
"List/length" => Dhall::Forall.new(
|
1054
|
+
var: "a",
|
1055
|
+
type: Dhall::Variable["Type"],
|
1056
|
+
body: Dhall::Forall.of_arguments(
|
1057
|
+
Dhall::Application.new(
|
1058
|
+
function: Dhall::Variable["List"],
|
1059
|
+
argument: Dhall::Variable["a"]
|
1060
|
+
),
|
1061
|
+
body: Dhall::Variable["Natural"]
|
1062
|
+
)
|
1063
|
+
),
|
1064
|
+
"List/head" => Dhall::Forall.new(
|
1065
|
+
var: "a",
|
1066
|
+
type: Dhall::Variable["Type"],
|
1067
|
+
body: Dhall::Forall.of_arguments(
|
1068
|
+
Dhall::Application.new(
|
1069
|
+
function: Dhall::Variable["List"],
|
1070
|
+
argument: Dhall::Variable["a"]
|
1071
|
+
),
|
1072
|
+
body: Dhall::Application.new(
|
1073
|
+
function: Dhall::Variable["Optional"],
|
1074
|
+
argument: Dhall::Variable["a"]
|
1075
|
+
)
|
1076
|
+
)
|
1077
|
+
),
|
1078
|
+
"List/last" => Dhall::Forall.new(
|
1079
|
+
var: "a",
|
1080
|
+
type: Dhall::Variable["Type"],
|
1081
|
+
body: Dhall::Forall.of_arguments(
|
1082
|
+
Dhall::Application.new(
|
1083
|
+
function: Dhall::Variable["List"],
|
1084
|
+
argument: Dhall::Variable["a"]
|
1085
|
+
),
|
1086
|
+
body: Dhall::Application.new(
|
1087
|
+
function: Dhall::Variable["Optional"],
|
1088
|
+
argument: Dhall::Variable["a"]
|
1089
|
+
)
|
1090
|
+
)
|
1091
|
+
),
|
1092
|
+
"List/indexed" => Dhall::Forall.new(
|
1093
|
+
var: "a",
|
1094
|
+
type: Dhall::Variable["Type"],
|
1095
|
+
body: Dhall::Forall.of_arguments(
|
1096
|
+
Dhall::Application.new(
|
1097
|
+
function: Dhall::Variable["List"],
|
1098
|
+
argument: Dhall::Variable["a"]
|
1099
|
+
),
|
1100
|
+
body: Dhall::Application.new(
|
1101
|
+
function: Dhall::Variable["List"],
|
1102
|
+
argument: Dhall::RecordType.new(
|
1103
|
+
record: {
|
1104
|
+
"index" => Dhall::Variable["Natural"],
|
1105
|
+
"value" => Dhall::Variable["a"]
|
1106
|
+
}
|
1107
|
+
)
|
1108
|
+
)
|
1109
|
+
)
|
1110
|
+
),
|
1111
|
+
"List/reverse" => Dhall::Forall.new(
|
1112
|
+
var: "a",
|
1113
|
+
type: Dhall::Variable["Type"],
|
1114
|
+
body: Dhall::Forall.of_arguments(
|
1115
|
+
Dhall::Application.new(
|
1116
|
+
function: Dhall::Variable["List"],
|
1117
|
+
argument: Dhall::Variable["a"]
|
1118
|
+
),
|
1119
|
+
body: Dhall::Application.new(
|
1120
|
+
function: Dhall::Variable["List"],
|
1121
|
+
argument: Dhall::Variable["a"]
|
1122
|
+
)
|
1123
|
+
)
|
1124
|
+
),
|
1125
|
+
"Optional/fold" => Dhall::Forall.new(
|
1126
|
+
var: "a",
|
1127
|
+
type: Dhall::Variable["Type"],
|
1128
|
+
body: Dhall::Forall.of_arguments(
|
1129
|
+
Dhall::Application.new(
|
1130
|
+
function: Dhall::Variable["Optional"],
|
1131
|
+
argument: Dhall::Variable["a"]
|
1132
|
+
),
|
1133
|
+
body: Dhall::Forall.new(
|
1134
|
+
var: "optional",
|
1135
|
+
type: Dhall::Variable["Type"],
|
1136
|
+
body: Dhall::Forall.new(
|
1137
|
+
var: "just",
|
1138
|
+
type: Dhall::Forall.of_arguments(
|
1139
|
+
Dhall::Variable["a"],
|
1140
|
+
body: Dhall::Variable["optional"]
|
1141
|
+
),
|
1142
|
+
body: Dhall::Forall.new(
|
1143
|
+
var: "nothing",
|
1144
|
+
type: Dhall::Variable["optional"],
|
1145
|
+
body: Dhall::Variable["optional"]
|
1146
|
+
)
|
1147
|
+
)
|
1148
|
+
)
|
1149
|
+
)
|
1150
|
+
),
|
1151
|
+
"Optional/build" => Dhall::Forall.new(
|
1152
|
+
var: "a",
|
1153
|
+
type: Dhall::Variable["Type"],
|
1154
|
+
body: Dhall::Forall.of_arguments(
|
1155
|
+
Dhall::Forall.new(
|
1156
|
+
var: "optional",
|
1157
|
+
type: Dhall::Variable["Type"],
|
1158
|
+
body: Dhall::Forall.new(
|
1159
|
+
var: "just",
|
1160
|
+
type: Dhall::Forall.of_arguments(
|
1161
|
+
Dhall::Variable["a"],
|
1162
|
+
body: Dhall::Variable["optional"]
|
1163
|
+
),
|
1164
|
+
body: Dhall::Forall.new(
|
1165
|
+
var: "nothing",
|
1166
|
+
type: Dhall::Variable["optional"],
|
1167
|
+
body: Dhall::Variable["optional"]
|
1168
|
+
)
|
1169
|
+
)
|
1170
|
+
),
|
1171
|
+
body: Dhall::Application.new(
|
1172
|
+
function: Dhall::Variable["Optional"],
|
1173
|
+
argument: Dhall::Variable["a"]
|
1174
|
+
)
|
1175
|
+
)
|
1176
|
+
),
|
1177
|
+
"Integer/show" => Dhall::Forall.of_arguments(
|
1178
|
+
Dhall::Variable["Integer"],
|
1179
|
+
body: Dhall::Variable["Text"]
|
1180
|
+
),
|
1181
|
+
"Integer/toDouble" => Dhall::Forall.of_arguments(
|
1182
|
+
Dhall::Variable["Integer"],
|
1183
|
+
body: Dhall::Variable["Double"]
|
1184
|
+
),
|
1185
|
+
"Double/show" => Dhall::Forall.of_arguments(
|
1186
|
+
Dhall::Variable["Double"],
|
1187
|
+
body: Dhall::Variable["Text"]
|
1188
|
+
)
|
1189
|
+
}.freeze
|
1190
|
+
|
1191
|
+
class Builtin
|
1192
|
+
TypeChecker.register self, Dhall::Builtin
|
1193
|
+
|
1194
|
+
def initialize(builtin)
|
1195
|
+
@expr = builtin
|
1196
|
+
@name = builtin.as_json
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
def annotate(*)
|
1200
|
+
Dhall::TypeAnnotation.new(
|
1201
|
+
value: @expr,
|
1202
|
+
type: BUILTIN_TYPES.fetch(@name) do
|
1203
|
+
raise TypeError, "Unknown Builtin #{@name}"
|
1204
|
+
end
|
1205
|
+
)
|
1206
|
+
end
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
end
|