dhall 0.1.0 → 0.5.1

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.
@@ -16,23 +16,32 @@ module Dhall
16
16
  module Resolvers
17
17
  ReadPathSources = lambda do |sources|
18
18
  sources.map do |source|
19
- Promise.resolve(nil).then { source.pathname.binread }
19
+ Util::LazyPromise.new { source.pathname.binread }
20
+ end
21
+ end
22
+
23
+ ReadEnvironmentSources = lambda do |sources|
24
+ sources.map do |source|
25
+ Util::LazyPromise.new do
26
+ ENV.fetch(source.var) do
27
+ raise ImportFailedException, "No #{source}"
28
+ end
29
+ end
20
30
  end
21
31
  end
22
32
 
23
33
  PreflightCORS = lambda do |source, parent_origin|
34
+ timeout = source.deadline.timeout
24
35
  uri = source.uri
25
36
  if parent_origin != "localhost" && parent_origin != source.origin
26
37
  req = Net::HTTP::Options.new(uri)
27
38
  req["Origin"] = parent_origin
28
39
  req["Access-Control-Request-Method"] = "GET"
29
40
  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) }
41
+ source.headers.to_a.map { |h|
42
+ (h.fetch("header") { h.fetch("mapKey") }).to_s
43
+ }.join(",")
44
+ r = Util.net_http_req_with_timeout(uri, req, timeout: timeout)
36
45
 
37
46
  raise ImportFailedException, source if r.code != "200"
38
47
  unless r["Access-Control-Allow-Origin"] == parent_origin ||
@@ -44,18 +53,21 @@ module Dhall
44
53
 
45
54
  ReadHttpSources = lambda do |sources, parent_origin|
46
55
  sources.map do |source|
47
- Promise.resolve(nil).then do
56
+ Util::LazyPromise.new do
48
57
  PreflightCORS.call(source, parent_origin)
58
+ timeout = source.deadline.timeout
49
59
  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
60
+ r = loop do
61
+ req = Net::HTTP::Get.new(uri)
62
+ source.headers.each do |header|
63
+ req[(header.fetch("header") { header.fetch("mapKey") }).to_s] =
64
+ (header.fetch("value") { header.fetch("mapValue") }).to_s
65
+ end
66
+ r = Util.net_http_req_with_timeout(uri, req, timeout: timeout)
67
+
68
+ break r unless ["301", "302", "303", "307", "308"].include?(r.code)
69
+ uri = URI(r["Location"])
53
70
  end
54
- r = Net::HTTP.start(
55
- uri.hostname,
56
- uri.port,
57
- use_ssl: uri.scheme == "https"
58
- ) { |http| http.request(req) }
59
71
 
60
72
  raise ImportFailedException, source if r.code != "200"
61
73
  r.body
@@ -63,6 +75,18 @@ module Dhall
63
75
  end
64
76
  end
65
77
 
78
+ StandardReadHttpSources = lambda do |sources, parent_origin|
79
+ ReadHttpSources.call(sources, parent_origin).map do |source_promise|
80
+ source_promise.then do |s|
81
+ s = s.dup.force_encoding("UTF-8")
82
+ unless s.valid_encoding?
83
+ raise ImportFailedException, "#{s.inspect} is not valid UTF-8"
84
+ end
85
+ s
86
+ end
87
+ end
88
+ end
89
+
66
90
  RejectSources = lambda do |sources|
67
91
  sources.map do |source|
68
92
  Promise.new.reject(ImportBannedException.new(source))
@@ -74,7 +98,7 @@ module Dhall
74
98
  path_reader: ReadPathSources,
75
99
  http_reader: ReadHttpSources,
76
100
  https_reader: http_reader,
77
- public_gateway: "cloudflare-ipfs.com"
101
+ public_gateway: URI("https://cloudflare-ipfs.com")
78
102
  )
79
103
  @path_reader = path_reader
80
104
  @http_reader = http_reader
@@ -89,7 +113,7 @@ module Dhall
89
113
  def call(sources)
90
114
  @path_reader.call(sources).map.with_index do |promise, idx|
91
115
  source = sources[idx]
92
- if source.is_a?(Import::AbsolutePath) &&
116
+ if source.canonical.is_a?(Import::AbsolutePath) &&
93
117
  ["ipfs", "ipns"].include?(source.path.first)
94
118
  gateway_fallback(source, promise)
95
119
  else
@@ -107,7 +131,7 @@ module Dhall
107
131
  def gateway_fallback(source, promise)
108
132
  promise.catch {
109
133
  @http_reader.call([
110
- source.to_uri(Import::Http, "localhost:8000")
134
+ source.to_uri(Import::Http, URI("http://localhost:8000"))
111
135
  ], "localhost").first
112
136
  }.catch do
113
137
  @https_reader.call([
@@ -117,17 +141,67 @@ module Dhall
117
141
  end
118
142
  end
119
143
 
144
+ module NoCache
145
+ def self.fetch(*)
146
+ yield
147
+ end
148
+ end
149
+
150
+ class RamCache
151
+ def initialize
152
+ @cache = {}
153
+ end
154
+
155
+ def fetch(key, &block)
156
+ return @cache[key] if @cache.key?(key)
157
+
158
+ Promise.resolve(nil).then(&block).then do |result|
159
+ @cache[key] = result
160
+ end
161
+ end
162
+ end
163
+
164
+ class StandardFileCache
165
+ def initialize(
166
+ dir=Pathname.new(ENV.fetch(
167
+ "XDG_CACHE_HOME", ENV.fetch("HOME") + "/.cache/"
168
+ )) + "dhall/"
169
+ )
170
+ dir.mkpath
171
+ @dir = dir
172
+ @ram = RamCache.new
173
+ end
174
+
175
+ def fetch(key, &block)
176
+ if key.is_a?(String) && key.start_with?("sha256:")
177
+ file = @dir + key.sub(/^sha256:/, "1220")
178
+ return Dhall.from_binary(file.binread) if file.exist?
179
+
180
+ Promise.resolve(nil).then(&block).then do |result|
181
+ file.open("wb") { |fh| fh.write(result.to_cbor) }
182
+ result
183
+ end
184
+ else
185
+ @ram.fetch(key, &block)
186
+ end
187
+ end
188
+ end
189
+
120
190
  class ResolutionSet
121
- def initialize(reader)
191
+ def initialize(reader, max_depth:)
122
192
  @reader = reader
193
+ @max_depth = max_depth
123
194
  @parents = []
124
195
  @set = Hash.new { |h, k| h[k] = [] }
125
196
  end
126
197
 
127
198
  def register(source)
128
199
  p = Promise.new
129
- if @parents.include?(source)
200
+ if @parents.include?(source.canonical)
130
201
  p.reject(ImportLoopException.new(source))
202
+ elsif @parents.length + 1 > @max_depth
203
+ msg = "Max import depth of #{@max_depth} exceeded"
204
+ p.reject(ImportFailedException.new(msg))
131
205
  else
132
206
  @set[source] << p
133
207
  end
@@ -141,6 +215,8 @@ module Dhall
141
215
 
142
216
  def reader
143
217
  lambda do |sources|
218
+ raise TimeoutException if sources.any? { |s| s.deadline.exceeded? }
219
+
144
220
  if @reader.arity == 2
145
221
  @reader.call(sources, @parents.last&.origin || "localhost")
146
222
  else
@@ -159,28 +235,66 @@ module Dhall
159
235
  end
160
236
  end
161
237
 
238
+ class SourceWithDeadline < SimpleDelegator
239
+ attr_reader :deadline
240
+
241
+ def initialize(source, deadline)
242
+ @source = source
243
+ @deadline = deadline
244
+
245
+ super(source)
246
+ end
247
+
248
+ def to_uri(*args)
249
+ self.class.new(super, deadline)
250
+ end
251
+ end
252
+
162
253
  class Standard
254
+ attr_reader :deadline
255
+
163
256
  def initialize(
164
257
  path_reader: ReadPathSources,
165
- http_reader: ReadHttpSources,
166
- https_reader: http_reader
258
+ http_reader: StandardReadHttpSources,
259
+ https_reader: http_reader,
260
+ environment_reader: ReadEnvironmentSources,
261
+ cache: StandardFileCache.new,
262
+ max_depth: Float::INFINITY
167
263
  )
168
- @path_resolutions = ResolutionSet.new(path_reader)
169
- @http_resolutions = ResolutionSet.new(http_reader)
170
- @https_resolutions = ResolutionSet.new(https_reader)
171
- @cache = {}
264
+ @path_resolutions = ResolutionSet.new(path_reader, max_depth: max_depth)
265
+ @http_resolutions = ResolutionSet.new(http_reader, max_depth: max_depth)
266
+ @https_resolutions = ResolutionSet.new(https_reader, max_depth: max_depth)
267
+ @env_resolutions = ResolutionSet.new(
268
+ environment_reader, max_depth: max_depth
269
+ )
270
+ @deadline = Util::NoDeadline.new
271
+ @cache = cache
272
+ end
273
+
274
+ def with_deadline(deadline)
275
+ dup.tap do |c|
276
+ c.instance_eval do
277
+ @deadline = deadline
278
+ end
279
+ end
172
280
  end
173
281
 
174
282
  def cache_fetch(key, &fallback)
175
283
  @cache.fetch(key) do
176
- Promise.resolve(nil).then(&fallback).then do |result|
177
- @cache[key] = result
178
- end
284
+ Promise.resolve(nil).then(&fallback)
179
285
  end
180
286
  end
181
287
 
182
288
  def resolve_path(path_source)
183
- @path_resolutions.register(path_source)
289
+ @path_resolutions.register(
290
+ SourceWithDeadline.new(path_source, @deadline)
291
+ )
292
+ end
293
+
294
+ def resolve_environment(env_source)
295
+ @env_resolutions.register(
296
+ SourceWithDeadline.new(env_source, @deadline)
297
+ )
184
298
  end
185
299
 
186
300
  def resolve_http(http_source)
@@ -188,8 +302,9 @@ module Dhall
188
302
  resolver: self,
189
303
  relative_to: Dhall::Import::RelativePath.new
190
304
  ).then do |headers|
305
+ source = http_source.with(headers: headers.normalize)
191
306
  @http_resolutions.register(
192
- http_source.with(headers: headers.normalize)
307
+ SourceWithDeadline.new(source, @deadline)
193
308
  )
194
309
  end
195
310
  end
@@ -199,8 +314,9 @@ module Dhall
199
314
  resolver: self,
200
315
  relative_to: Dhall::Import::RelativePath.new
201
316
  ).then do |headers|
317
+ source = https_source.with(headers: headers.normalize)
202
318
  @https_resolutions.register(
203
- https_source.with(headers: headers.normalize)
319
+ SourceWithDeadline.new(source, @deadline)
204
320
  )
205
321
  end
206
322
  end
@@ -208,6 +324,7 @@ module Dhall
208
324
  def finish!
209
325
  [
210
326
  @path_resolutions,
327
+ @env_resolutions,
211
328
  @http_resolutions,
212
329
  @https_resolutions
213
330
  ].each do |rset|
@@ -220,6 +337,7 @@ module Dhall
220
337
  dup.tap do |c|
221
338
  c.instance_eval do
222
339
  @path_resolutions = @path_resolutions.child(parent_source)
340
+ @env_resolutions = @env_resolutions.child(parent_source)
223
341
  @http_resolutions = @http_resolutions.child(parent_source)
224
342
  @https_resolutions = @https_resolutions.child(parent_source)
225
343
  end
@@ -232,27 +350,36 @@ module Dhall
232
350
  path_reader: ReadPathSources,
233
351
  http_reader: ReadHttpSources,
234
352
  https_reader: http_reader,
235
- ipfs_public_gateway: "cloudflare-ipfs.com"
353
+ environment_reader: ReadEnvironmentSources,
354
+ ipfs_public_gateway: URI("https://cloudflare-ipfs.com"),
355
+ cache: RamCache.new,
356
+ max_depth: 50
236
357
  )
237
358
  super(
238
- path_reader: ReadPathAndIPFSSources.new(
359
+ path_reader: ReadPathAndIPFSSources.new(
239
360
  path_reader: path_reader,
240
361
  http_reader: http_reader,
241
362
  https_reader: https_reader,
242
363
  public_gateway: ipfs_public_gateway
243
364
  ),
244
- http_reader: http_reader,
245
- https_reader: https_reader
365
+ http_reader: http_reader, https_reader: https_reader, cache: cache,
366
+ environment_reader: environment_reader, max_depth: max_depth
246
367
  )
247
368
  end
248
369
  end
249
370
 
250
371
  class LocalOnly < Standard
251
- def initialize(path_reader: ReadPathSources)
372
+ def initialize(
373
+ path_reader: ReadPathSources,
374
+ environment_reader: ReadEnvironmentSources,
375
+ max_depth: 50
376
+ )
252
377
  super(
253
- path_reader: path_reader,
254
- http_reader: RejectSources,
255
- https_reader: RejectSources
378
+ path_reader: path_reader,
379
+ environment_reader: environment_reader,
380
+ http_reader: RejectSources,
381
+ https_reader: RejectSources,
382
+ max_depth: max_depth
256
383
  )
257
384
  end
258
385
  end
@@ -260,9 +387,10 @@ module Dhall
260
387
  class None < Default
261
388
  def initialize
262
389
  super(
263
- path_reader: RejectSources,
264
- http_reader: RejectSources,
265
- https_reader: RejectSources
390
+ path_reader: RejectSources,
391
+ environment_reader: RejectSources,
392
+ http_reader: RejectSources,
393
+ https_reader: RejectSources
266
394
  )
267
395
  end
268
396
  end
@@ -291,9 +419,25 @@ module Dhall
291
419
  ).then { |h| @expr.with(h) }
292
420
  end
293
421
 
422
+ class ImportAsLocationResolver < ExpressionResolver
423
+ def resolve(resolver:, relative_to:)
424
+ Promise.resolve(nil).then do
425
+ @expr.real_path(relative_to).location
426
+ end
427
+ end
428
+ end
429
+
294
430
  class ImportResolver < ExpressionResolver
295
431
  register_for Import
296
432
 
433
+ def self.new(expr)
434
+ if expr.import_type == Import::AsLocation
435
+ ImportAsLocationResolver.new(expr)
436
+ else
437
+ super
438
+ end
439
+ end
440
+
297
441
  def resolve(resolver:, relative_to:)
298
442
  Promise.resolve(nil).then do
299
443
  resolver.cache_fetch(@expr.cache_key(relative_to)) do
@@ -305,7 +449,9 @@ module Dhall
305
449
  def resolve_raw(resolver:, relative_to:)
306
450
  real_path = @expr.real_path(relative_to)
307
451
  real_path.resolve(resolver).then do |result|
308
- @expr.parse_and_check(result).resolve(
452
+ @expr.parse_resolve_check(
453
+ result,
454
+ deadline: resolver.deadline,
309
455
  resolver: resolver.child(real_path),
310
456
  relative_to: real_path
311
457
  )
@@ -316,9 +462,15 @@ module Dhall
316
462
  class FallbackResolver < ExpressionResolver
317
463
  register_for Operator::ImportFallback
318
464
 
319
- def resolve(**kwargs)
320
- ExpressionResolver.for(@expr.lhs).resolve(**kwargs).catch do
321
- ExpressionResolver.for(@expr.rhs).resolve(**kwargs)
465
+ def resolve(resolver:, relative_to:)
466
+ ExpressionResolver.for(@expr.lhs).resolve(
467
+ resolver: resolver,
468
+ relative_to: relative_to
469
+ ).catch do
470
+ @expr.rhs.resolve(
471
+ resolver: resolver.child(Import::MissingImport.new),
472
+ relative_to: relative_to
473
+ )
322
474
  end
323
475
  end
324
476
  end
@@ -12,7 +12,7 @@ module Dhall
12
12
 
13
13
  def self.assert_type(expr, assertion, message, context:)
14
14
  aexpr = self.for(expr).annotate(context)
15
- type = aexpr.type
15
+ type = aexpr.type.normalize
16
16
  raise TypeError, "#{message}: #{type}" unless assertion === type
17
17
  aexpr
18
18
  end
@@ -20,7 +20,9 @@ module Dhall
20
20
  def self.assert_types_match(a, b, message, context:)
21
21
  atype = self.for(a).annotate(context).type
22
22
  btype = self.for(b).annotate(context).type
23
- raise TypeError, "#{message}: #{atype}, #{btype}" unless atype == btype
23
+ unless atype.normalize == btype.normalize
24
+ raise TypeError, "#{message}: #{atype}, #{btype}"
25
+ end
24
26
  atype
25
27
  end
26
28
 
@@ -40,9 +42,13 @@ module Dhall
40
42
  @typecheckers[node_type] ||= [typechecker, extras]
41
43
  end
42
44
 
43
- def self.type_of(expr)
45
+ def self.annotate(expr)
44
46
  return if expr.nil?
45
- TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
47
+ TypeChecker.for(expr).annotate(TypeChecker::Context.new)
48
+ end
49
+
50
+ def self.type_of(expr)
51
+ annotate(expr)&.type
46
52
  end
47
53
 
48
54
  class Context
@@ -72,9 +78,9 @@ module Dhall
72
78
  end
73
79
 
74
80
  KINDS = [
75
- Dhall::Variable["Type"],
76
- Dhall::Variable["Kind"],
77
- Dhall::Variable["Sort"]
81
+ Builtins[:Type],
82
+ Builtins[:Kind],
83
+ Builtins[:Sort]
78
84
  ].freeze
79
85
 
80
86
  class Variable
@@ -84,38 +90,12 @@ module Dhall
84
90
  @var = var
85
91
  end
86
92
 
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
93
  def annotate(context)
114
94
  raise TypeError, "Sort has no Type, Kind, or Sort" if @var.name == "Sort"
115
95
 
116
96
  Dhall::TypeAnnotation.new(
117
97
  value: @var,
118
- type: BUILTIN.fetch(@var.name) { context.fetch(@var) }
98
+ type: context.fetch(@var)
119
99
  )
120
100
  end
121
101
  end
@@ -130,6 +110,7 @@ module Dhall
130
110
  def initialize(lit)
131
111
  @lit = lit
132
112
  @type = Dhall::Variable[lit.class.name.split(/::/).last]
113
+ @type = Builtins[@type.name.to_sym] || @type
133
114
  end
134
115
 
135
116
  def annotate(*)
@@ -170,14 +151,14 @@ module Dhall
170
151
  def annotate(context)
171
152
  chunks = Chunks.new(@lit.chunks).map { |c|
172
153
  TypeChecker.for(c).annotate(context).tap do |annotated|
173
- TypeChecker.assert annotated.type, Dhall::Variable["Text"],
154
+ TypeChecker.assert annotated.type, Builtins[:Text],
174
155
  "Cannot interpolate #{annotated.type}"
175
156
  end
176
157
  }.to_a
177
158
 
178
159
  Dhall::TypeAnnotation.new(
179
160
  value: @lit.with(chunks: chunks),
180
- type: Dhall::Variable["Text"]
161
+ type: Builtins[:Text]
181
162
  )
182
163
  end
183
164
  end
@@ -194,9 +175,9 @@ module Dhall
194
175
 
195
176
  class AnnotatedIf
196
177
  def initialize(expr, apred, athen, aelse, context:)
197
- TypeChecker.assert apred.type, Dhall::Variable["Bool"],
178
+ TypeChecker.assert apred.type, Builtins[:Bool],
198
179
  "If must have a predicate of type Bool"
199
- TypeChecker.assert_type athen.type, Dhall::Variable["Type"],
180
+ TypeChecker.assert_type athen.type, Builtins[:Type],
200
181
  "If branches must have types of type Type",
201
182
  context: context
202
183
  TypeChecker.assert aelse.type, athen.type,
@@ -225,13 +206,13 @@ module Dhall
225
206
 
226
207
  class Operator
227
208
  {
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"]
209
+ Dhall::Operator::And => Builtins[:Bool],
210
+ Dhall::Operator::Or => Builtins[:Bool],
211
+ Dhall::Operator::Equal => Builtins[:Bool],
212
+ Dhall::Operator::NotEqual => Builtins[:Bool],
213
+ Dhall::Operator::Plus => Builtins[:Natural],
214
+ Dhall::Operator::Times => Builtins[:Natural],
215
+ Dhall::Operator::TextConcatenate => Builtins[:Text]
235
216
  }.each do |node_type, type|
236
217
  TypeChecker.register self, node_type, type
237
218
  end
@@ -258,6 +239,31 @@ module Dhall
258
239
  end
259
240
  end
260
241
 
242
+ class OperatorEquivalent
243
+ TypeChecker.register self, Dhall::Operator::Equivalent
244
+
245
+ def initialize(expr)
246
+ @expr = expr
247
+ @lhs = expr.lhs
248
+ @rhs = expr.rhs
249
+ end
250
+
251
+ def annotate(context)
252
+ type = TypeChecker.assert_types_match @lhs, @rhs,
253
+ "arguments do not match",
254
+ context: context
255
+
256
+ TypeChecker.assert_type type, Builtins[:Type],
257
+ "arguments are not terms",
258
+ context: context
259
+
260
+ Dhall::TypeAnnotation.new(
261
+ value: @expr.with(lhs: @lhs.annotate(type), rhs: @rhs.annotate(type)),
262
+ type: Builtins[:Type]
263
+ )
264
+ end
265
+ end
266
+
261
267
  class OperatorListConcatenate
262
268
  TypeChecker.register self, Dhall::Operator::ListConcatenate
263
269
 
@@ -270,7 +276,7 @@ module Dhall
270
276
  module IsList
271
277
  def self.===(other)
272
278
  other.is_a?(Dhall::Application) &&
273
- other.function == Dhall::Variable["List"]
279
+ other.function == Builtins[:List]
274
280
  end
275
281
  end
276
282
 
@@ -334,10 +340,6 @@ module Dhall
334
340
  "RecursiveRecordMerge got",
335
341
  context: context
336
342
 
337
- TypeChecker.assert_types_match annotated_lhs.type, annotated_rhs.type,
338
- "RecursiveRecordMerge got mixed kinds",
339
- context: context
340
-
341
343
  [annotated_lhs, annotated_rhs]
342
344
  end
343
345
 
@@ -361,12 +363,6 @@ module Dhall
361
363
  end
362
364
 
363
365
  def annotate(context)
364
- kind = TypeChecker.assert_types_match(
365
- @expr.lhs, @expr.rhs,
366
- "RecursiveRecordTypeMerge mixed kinds",
367
- context: context
368
- )
369
-
370
366
  type = @expr.lhs.deep_merge_type(@expr.rhs)
371
367
 
372
368
  TypeChecker.assert type, Dhall::RecordType,
@@ -375,7 +371,13 @@ module Dhall
375
371
  # Annotate to sanity check
376
372
  TypeChecker.for(type).annotate(context)
377
373
 
378
- Dhall::TypeAnnotation.new(value: @expr, type: kind)
374
+ Dhall::TypeAnnotation.new(value: @expr, type: kind(context))
375
+ end
376
+
377
+ def kind(context)
378
+ lhs_kind = KINDS.index(TypeChecker.for(@expr.lhs).annotate(context).type)
379
+ rhs_kind = KINDS.index(TypeChecker.for(@expr.rhs).annotate(context).type)
380
+ KINDS[[lhs_kind, rhs_kind].max]
379
381
  end
380
382
  end
381
383
 
@@ -383,11 +385,17 @@ module Dhall
383
385
  TypeChecker.register self, Dhall::EmptyList
384
386
 
385
387
  def initialize(expr)
386
- @expr = expr
388
+ @expr = expr.with(type: expr.type.normalize)
387
389
  end
388
390
 
389
391
  def annotate(context)
390
- TypeChecker.assert_type @expr.element_type, Dhall::Variable["Type"],
392
+ TypeChecker.assert @expr.type, Dhall::Application,
393
+ "EmptyList unknown type #{@expr.type.inspect}"
394
+
395
+ TypeChecker.assert @expr.type.function, Builtins[:List],
396
+ "EmptyList unknown type #{@expr.type.inspect}"
397
+
398
+ TypeChecker.assert_type @expr.element_type, Builtins[:Type],
391
399
  "EmptyList element type not of type Type",
392
400
  context: context
393
401
 
@@ -408,16 +416,16 @@ module Dhall
408
416
  end
409
417
 
410
418
  def annotation
411
- list = @alist.with(element_type: element_type)
419
+ list = @alist.with(type: Builtins[:List].call(element_type))
412
420
  Dhall::TypeAnnotation.new(type: list.type, value: list)
413
421
  end
414
422
 
415
423
  def element_type
416
- @alist.first.value&.type || @alist.element_type
424
+ (@alist.first.value&.type || @alist.element_type).normalize
417
425
  end
418
426
 
419
427
  def element_types
420
- @alist.to_a.map(&:type)
428
+ @alist.to_a.map(&:type).map(&:normalize)
421
429
  end
422
430
  end
423
431
 
@@ -430,7 +438,7 @@ module Dhall
430
438
  Util::ArrayOf.new(alist.element_type),
431
439
  "Non-homogenous List"
432
440
 
433
- TypeChecker.assert_type alist.element_type, Dhall::Variable["Type"],
441
+ TypeChecker.assert_type alist.element_type, Builtins[:Type],
434
442
  "List type not of type Type", context: context
435
443
 
436
444
  alist.annotation
@@ -447,7 +455,7 @@ module Dhall
447
455
  def annotate(context)
448
456
  TypeChecker.assert(
449
457
  TypeChecker.for(@expr.value_type).annotate(context).type,
450
- Dhall::Variable["Type"],
458
+ Builtins[:Type],
451
459
  "OptionalNone element type not of type Type"
452
460
  )
453
461
 
@@ -469,7 +477,7 @@ module Dhall
469
477
  some = asome.with(value_type: asome.value.type)
470
478
 
471
479
  type_type = TypeChecker.for(some.value_type).annotate(context).type
472
- TypeChecker.assert type_type, Dhall::Variable["Type"],
480
+ TypeChecker.assert type_type, Builtins[:Type],
473
481
  "Some type not of type Type, was: #{type_type}"
474
482
 
475
483
  Dhall::TypeAnnotation.new(type: some.type, value: some)
@@ -486,20 +494,27 @@ module Dhall
486
494
  def annotate(*)
487
495
  Dhall::TypeAnnotation.new(
488
496
  value: @expr,
489
- type: Dhall::Variable["Type"]
497
+ type: Builtins[:Type]
490
498
  )
491
499
  end
492
500
  end
493
501
 
494
502
  class AnonymousType
495
503
  TypeChecker.register self, Dhall::RecordType
496
- TypeChecker.register self, Dhall::UnionType
497
504
 
498
505
  def initialize(type)
499
506
  @type = type
500
507
  end
501
508
 
502
509
  def annotate(context)
510
+ kinds = check(context)
511
+ type = kinds.max_by { |k| KINDS.index(k) } || KINDS.first
512
+ Dhall::TypeAnnotation.new(value: @type, type: type)
513
+ end
514
+
515
+ protected
516
+
517
+ def check(context)
503
518
  kinds = @type.record.values.compact.map do |mtype|
504
519
  TypeChecker.for(mtype).annotate(context).type
505
520
  end
@@ -507,6 +522,16 @@ module Dhall
507
522
  TypeChecker.assert (kinds - KINDS), [],
508
523
  "AnonymousType field kind not one of #{KINDS}"
509
524
 
525
+ kinds
526
+ end
527
+ end
528
+
529
+ class UnionType < AnonymousType
530
+ TypeChecker.register self, Dhall::UnionType
531
+
532
+ def annotate(context)
533
+ kinds = check(context)
534
+
510
535
  TypeChecker.assert kinds, Util::ArrayAllTheSame,
511
536
  "AnonymousType field kinds not all the same"
512
537
 
@@ -562,12 +587,13 @@ module Dhall
562
587
 
563
588
  class Selector
564
589
  def self.for(annotated_record)
565
- if annotated_record.type == Dhall::Variable["Type"]
590
+ typ = annotated_record.type.normalize
591
+ if KINDS.include?(typ)
566
592
  TypeSelector.new(annotated_record.value)
567
- elsif annotated_record.type.class == Dhall::RecordType
568
- new(annotated_record.type)
593
+ elsif typ.class == Dhall::RecordType
594
+ new(typ)
569
595
  else
570
- raise TypeError, "RecordSelection on #{annotated_record.type}"
596
+ raise TypeError, "RecordSelection on #{typ}"
571
597
  end
572
598
  end
573
599
 
@@ -629,6 +655,52 @@ module Dhall
629
655
  end
630
656
  end
631
657
 
658
+ class RecordProjectionByExpression
659
+ TypeChecker.register self, Dhall::RecordProjectionByExpression
660
+
661
+ def initialize(projection)
662
+ @selector = projection.selector.normalize
663
+ @project_by_expression = projection
664
+ @project_by_keys = Dhall::RecordProjection.for(
665
+ @project_by_expression.record,
666
+ @selector.keys
667
+ )
668
+ end
669
+
670
+ def annotate(context)
671
+ TypeChecker.assert @selector, Dhall::RecordType,
672
+ "RecordProjectionByExpression on #{@selector.class}"
673
+
674
+ TypeChecker.assert_type @project_by_keys, @selector,
675
+ "Type doesn't match #{@selector}",
676
+ context: context
677
+
678
+ Dhall::TypeAnnotation.new(
679
+ value: @project_by_expression,
680
+ type: @selector
681
+ )
682
+ end
683
+ end
684
+
685
+ class Enum
686
+ TypeChecker.register self, Dhall::Enum
687
+
688
+ def initialize(enum)
689
+ @enum = enum
690
+ end
691
+
692
+ def annotate(context)
693
+ type = Dhall::UnionType.new(
694
+ alternatives: { @enum.tag => nil }
695
+ ).merge(@enum.alternatives)
696
+
697
+ # Annotate to sanity check
698
+ TypeChecker.for(type).annotate(context)
699
+
700
+ Dhall::TypeAnnotation.new(value: @enum, type: type)
701
+ end
702
+ end
703
+
632
704
  class Union
633
705
  TypeChecker.register self, Dhall::Union
634
706
 
@@ -654,6 +726,39 @@ module Dhall
654
726
  end
655
727
  end
656
728
 
729
+ class ToMap
730
+ TypeChecker.register self, Dhall::ToMap
731
+
732
+ def initialize(tomap)
733
+ @tomap = tomap
734
+ @record = TypeChecker.for(tomap.record)
735
+ end
736
+
737
+ def check_annotation(record_type)
738
+ if record_type.is_a?(Dhall::EmptyRecordType)
739
+ TypeChecker.assert @tomap.type, Dhall::Expression,
740
+ "toMap {=} has no annotation"
741
+ else
742
+ t = Types::MAP(v: record_type.record.values.first)
743
+
744
+ TypeChecker.assert t, (@tomap.type || t),
745
+ "toMap does not match annotation"
746
+ end
747
+ end
748
+
749
+ def annotate(context)
750
+ record_type = @record.annotate(context).type
751
+ TypeChecker.assert record_type, Dhall::RecordType,
752
+ "toMap on a non-record: #{record_type.inspect}"
753
+
754
+ TypeChecker.assert record_type.record.values, Util::ArrayAllTheSame,
755
+ "toMap heterogenous: #{record_type.inspect}"
756
+
757
+ type = check_annotation(record_type)
758
+ Dhall::TypeAnnotation.new(value: @tomap, type: type)
759
+ end
760
+ end
761
+
657
762
  class Merge
658
763
  TypeChecker.register self, Dhall::Merge
659
764
 
@@ -684,7 +789,7 @@ module Dhall
684
789
  end
685
790
 
686
791
  def keys
687
- @type.record.keys
792
+ Set.new(@type.record.keys)
688
793
  end
689
794
 
690
795
  def fetch_input_type(k)
@@ -727,7 +832,7 @@ module Dhall
727
832
 
728
833
  TypeChecker.assert(
729
834
  kind,
730
- Dhall::Variable["Type"],
835
+ Builtins[:Type],
731
836
  "Merge must have kind Type"
732
837
  )
733
838
 
@@ -735,8 +840,8 @@ module Dhall
735
840
  end
736
841
 
737
842
  def assert_union_and_handlers_match
738
- extras = @handlers.keys - @union.type.alternatives.keys
739
- TypeChecker.assert extras, [],
843
+ extras = @handlers.keys ^ @union.type.alternatives.keys
844
+ TypeChecker.assert extras.to_a, [],
740
845
  "Merge handlers unknown alternatives: #{extras}"
741
846
 
742
847
  @union.type.alternatives.each do |k, atype|
@@ -777,8 +882,6 @@ module Dhall
777
882
  raise TypeError, "FunctionType part of this is a term"
778
883
  end
779
884
 
780
- raise TypeError, "Dependent types are not allowed" if outkind > inkind
781
-
782
885
  if outkind.zero?
783
886
  Term.new
784
887
  else
@@ -866,8 +969,6 @@ module Dhall
866
969
  end
867
970
  end
868
971
 
869
- TypeChecker.register ->(blk) { LetIn.for(blk.unflatten) }, Dhall::LetBlock
870
-
871
972
  class LetIn
872
973
  TypeChecker.register self, Dhall::LetIn
873
974
 
@@ -933,11 +1034,58 @@ module Dhall
933
1034
  end
934
1035
  end
935
1036
 
1037
+ class Assertion
1038
+ TypeChecker.register self, Dhall::Assertion
1039
+
1040
+ def initialize(expr)
1041
+ @expr = expr
1042
+ @type = expr.type
1043
+ end
1044
+
1045
+ def annotate(context)
1046
+ TypeChecker.assert @type, Dhall::Operator::Equivalent,
1047
+ "assert expected === got: #{@type.class}"
1048
+
1049
+ TypeChecker.assert_type @type, Builtins[:Type],
1050
+ "=== expected to have type Type",
1051
+ context: context
1052
+
1053
+ TypeChecker.assert @type.lhs.normalize.to_binary,
1054
+ @type.rhs.normalize.to_binary,
1055
+ "assert equivalence not equivalent"
1056
+
1057
+ @expr.with(type: @type.normalize)
1058
+ end
1059
+ end
1060
+
936
1061
  BUILTIN_TYPES = {
1062
+ "Bool" => Builtins[:Type],
1063
+ "Type" => Builtins[:Kind],
1064
+ "Kind" => Builtins[:Sort],
1065
+ "Natural" => Builtins[:Type],
1066
+ "Integer" => Builtins[:Type],
1067
+ "Double" => Builtins[:Type],
1068
+ "Text" => Builtins[:Type],
1069
+ "List" => Dhall::Forall.of_arguments(
1070
+ Builtins[:Type],
1071
+ body: Builtins[:Type]
1072
+ ),
1073
+ "Optional" => Dhall::Forall.of_arguments(
1074
+ Builtins[:Type],
1075
+ body: Builtins[:Type]
1076
+ ),
1077
+ "None" => Dhall::Forall.new(
1078
+ var: "A",
1079
+ type: Builtins[:Type],
1080
+ body: Dhall::Application.new(
1081
+ function: Builtins[:Optional],
1082
+ argument: Dhall::Variable["A"]
1083
+ )
1084
+ ),
937
1085
  "Natural/build" => Dhall::Forall.of_arguments(
938
1086
  Dhall::Forall.new(
939
1087
  var: "natural",
940
- type: Dhall::Variable["Type"],
1088
+ type: Builtins[:Type],
941
1089
  body: Dhall::Forall.new(
942
1090
  var: "succ",
943
1091
  type: Dhall::Forall.of_arguments(
@@ -951,13 +1099,13 @@ module Dhall
951
1099
  )
952
1100
  )
953
1101
  ),
954
- body: Dhall::Variable["Natural"]
1102
+ body: Builtins[:Natural]
955
1103
  ),
956
1104
  "Natural/fold" => Dhall::Forall.of_arguments(
957
- Dhall::Variable["Natural"],
1105
+ Builtins[:Natural],
958
1106
  body: Dhall::Forall.new(
959
1107
  var: "natural",
960
- type: Dhall::Variable["Type"],
1108
+ type: Builtins[:Type],
961
1109
  body: Dhall::Forall.new(
962
1110
  var: "succ",
963
1111
  type: Dhall::Forall.of_arguments(
@@ -972,37 +1120,42 @@ module Dhall
972
1120
  )
973
1121
  )
974
1122
  ),
1123
+ "Natural/subtract" => Dhall::Forall.of_arguments(
1124
+ Builtins[:Natural],
1125
+ Builtins[:Natural],
1126
+ body: Builtins[:Natural]
1127
+ ),
975
1128
  "Natural/isZero" => Dhall::Forall.of_arguments(
976
- Dhall::Variable["Natural"],
977
- body: Dhall::Variable["Bool"]
1129
+ Builtins[:Natural],
1130
+ body: Builtins[:Bool]
978
1131
  ),
979
1132
  "Natural/even" => Dhall::Forall.of_arguments(
980
- Dhall::Variable["Natural"],
981
- body: Dhall::Variable["Bool"]
1133
+ Builtins[:Natural],
1134
+ body: Builtins[:Bool]
982
1135
  ),
983
1136
  "Natural/odd" => Dhall::Forall.of_arguments(
984
- Dhall::Variable["Natural"],
985
- body: Dhall::Variable["Bool"]
1137
+ Builtins[:Natural],
1138
+ body: Builtins[:Bool]
986
1139
  ),
987
1140
  "Natural/toInteger" => Dhall::Forall.of_arguments(
988
- Dhall::Variable["Natural"],
989
- body: Dhall::Variable["Integer"]
1141
+ Builtins[:Natural],
1142
+ body: Builtins[:Integer]
990
1143
  ),
991
1144
  "Natural/show" => Dhall::Forall.of_arguments(
992
- Dhall::Variable["Natural"],
993
- body: Dhall::Variable["Text"]
1145
+ Builtins[:Natural],
1146
+ body: Builtins[:Text]
994
1147
  ),
995
1148
  "Text/show" => Dhall::Forall.of_arguments(
996
- Dhall::Variable["Text"],
997
- body: Dhall::Variable["Text"]
1149
+ Builtins[:Text],
1150
+ body: Builtins[:Text]
998
1151
  ),
999
1152
  "List/build" => Dhall::Forall.new(
1000
1153
  var: "a",
1001
- type: Dhall::Variable["Type"],
1154
+ type: Builtins[:Type],
1002
1155
  body: Dhall::Forall.of_arguments(
1003
1156
  Dhall::Forall.new(
1004
1157
  var: "list",
1005
- type: Dhall::Variable["Type"],
1158
+ type: Builtins[:Type],
1006
1159
  body: Dhall::Forall.new(
1007
1160
  var: "cons",
1008
1161
  type: Dhall::Forall.of_arguments(
@@ -1018,22 +1171,22 @@ module Dhall
1018
1171
  )
1019
1172
  ),
1020
1173
  body: Dhall::Application.new(
1021
- function: Dhall::Variable["List"],
1174
+ function: Builtins[:List],
1022
1175
  argument: Dhall::Variable["a"]
1023
1176
  )
1024
1177
  )
1025
1178
  ),
1026
1179
  "List/fold" => Dhall::Forall.new(
1027
1180
  var: "a",
1028
- type: Dhall::Variable["Type"],
1181
+ type: Builtins[:Type],
1029
1182
  body: Dhall::Forall.of_arguments(
1030
1183
  Dhall::Application.new(
1031
- function: Dhall::Variable["List"],
1184
+ function: Builtins[:List],
1032
1185
  argument: Dhall::Variable["a"]
1033
1186
  ),
1034
1187
  body: Dhall::Forall.new(
1035
1188
  var: "list",
1036
- type: Dhall::Variable["Type"],
1189
+ type: Builtins[:Type],
1037
1190
  body: Dhall::Forall.new(
1038
1191
  var: "cons",
1039
1192
  type: Dhall::Forall.of_arguments(
@@ -1052,56 +1205,56 @@ module Dhall
1052
1205
  ),
1053
1206
  "List/length" => Dhall::Forall.new(
1054
1207
  var: "a",
1055
- type: Dhall::Variable["Type"],
1208
+ type: Builtins[:Type],
1056
1209
  body: Dhall::Forall.of_arguments(
1057
1210
  Dhall::Application.new(
1058
- function: Dhall::Variable["List"],
1211
+ function: Builtins[:List],
1059
1212
  argument: Dhall::Variable["a"]
1060
1213
  ),
1061
- body: Dhall::Variable["Natural"]
1214
+ body: Builtins[:Natural]
1062
1215
  )
1063
1216
  ),
1064
1217
  "List/head" => Dhall::Forall.new(
1065
1218
  var: "a",
1066
- type: Dhall::Variable["Type"],
1219
+ type: Builtins[:Type],
1067
1220
  body: Dhall::Forall.of_arguments(
1068
1221
  Dhall::Application.new(
1069
- function: Dhall::Variable["List"],
1222
+ function: Builtins[:List],
1070
1223
  argument: Dhall::Variable["a"]
1071
1224
  ),
1072
1225
  body: Dhall::Application.new(
1073
- function: Dhall::Variable["Optional"],
1226
+ function: Builtins[:Optional],
1074
1227
  argument: Dhall::Variable["a"]
1075
1228
  )
1076
1229
  )
1077
1230
  ),
1078
1231
  "List/last" => Dhall::Forall.new(
1079
1232
  var: "a",
1080
- type: Dhall::Variable["Type"],
1233
+ type: Builtins[:Type],
1081
1234
  body: Dhall::Forall.of_arguments(
1082
1235
  Dhall::Application.new(
1083
- function: Dhall::Variable["List"],
1236
+ function: Builtins[:List],
1084
1237
  argument: Dhall::Variable["a"]
1085
1238
  ),
1086
1239
  body: Dhall::Application.new(
1087
- function: Dhall::Variable["Optional"],
1240
+ function: Builtins[:Optional],
1088
1241
  argument: Dhall::Variable["a"]
1089
1242
  )
1090
1243
  )
1091
1244
  ),
1092
1245
  "List/indexed" => Dhall::Forall.new(
1093
1246
  var: "a",
1094
- type: Dhall::Variable["Type"],
1247
+ type: Builtins[:Type],
1095
1248
  body: Dhall::Forall.of_arguments(
1096
1249
  Dhall::Application.new(
1097
- function: Dhall::Variable["List"],
1250
+ function: Builtins[:List],
1098
1251
  argument: Dhall::Variable["a"]
1099
1252
  ),
1100
1253
  body: Dhall::Application.new(
1101
- function: Dhall::Variable["List"],
1254
+ function: Builtins[:List],
1102
1255
  argument: Dhall::RecordType.new(
1103
1256
  record: {
1104
- "index" => Dhall::Variable["Natural"],
1257
+ "index" => Builtins[:Natural],
1105
1258
  "value" => Dhall::Variable["a"]
1106
1259
  }
1107
1260
  )
@@ -1110,29 +1263,29 @@ module Dhall
1110
1263
  ),
1111
1264
  "List/reverse" => Dhall::Forall.new(
1112
1265
  var: "a",
1113
- type: Dhall::Variable["Type"],
1266
+ type: Builtins[:Type],
1114
1267
  body: Dhall::Forall.of_arguments(
1115
1268
  Dhall::Application.new(
1116
- function: Dhall::Variable["List"],
1269
+ function: Builtins[:List],
1117
1270
  argument: Dhall::Variable["a"]
1118
1271
  ),
1119
1272
  body: Dhall::Application.new(
1120
- function: Dhall::Variable["List"],
1273
+ function: Builtins[:List],
1121
1274
  argument: Dhall::Variable["a"]
1122
1275
  )
1123
1276
  )
1124
1277
  ),
1125
1278
  "Optional/fold" => Dhall::Forall.new(
1126
1279
  var: "a",
1127
- type: Dhall::Variable["Type"],
1280
+ type: Builtins[:Type],
1128
1281
  body: Dhall::Forall.of_arguments(
1129
1282
  Dhall::Application.new(
1130
- function: Dhall::Variable["Optional"],
1283
+ function: Builtins[:Optional],
1131
1284
  argument: Dhall::Variable["a"]
1132
1285
  ),
1133
1286
  body: Dhall::Forall.new(
1134
1287
  var: "optional",
1135
- type: Dhall::Variable["Type"],
1288
+ type: Builtins[:Type],
1136
1289
  body: Dhall::Forall.new(
1137
1290
  var: "just",
1138
1291
  type: Dhall::Forall.of_arguments(
@@ -1150,11 +1303,11 @@ module Dhall
1150
1303
  ),
1151
1304
  "Optional/build" => Dhall::Forall.new(
1152
1305
  var: "a",
1153
- type: Dhall::Variable["Type"],
1306
+ type: Builtins[:Type],
1154
1307
  body: Dhall::Forall.of_arguments(
1155
1308
  Dhall::Forall.new(
1156
1309
  var: "optional",
1157
- type: Dhall::Variable["Type"],
1310
+ type: Builtins[:Type],
1158
1311
  body: Dhall::Forall.new(
1159
1312
  var: "just",
1160
1313
  type: Dhall::Forall.of_arguments(
@@ -1169,28 +1322,38 @@ module Dhall
1169
1322
  )
1170
1323
  ),
1171
1324
  body: Dhall::Application.new(
1172
- function: Dhall::Variable["Optional"],
1325
+ function: Builtins[:Optional],
1173
1326
  argument: Dhall::Variable["a"]
1174
1327
  )
1175
1328
  )
1176
1329
  ),
1177
1330
  "Integer/show" => Dhall::Forall.of_arguments(
1178
- Dhall::Variable["Integer"],
1179
- body: Dhall::Variable["Text"]
1331
+ Builtins[:Integer],
1332
+ body: Builtins[:Text]
1180
1333
  ),
1181
1334
  "Integer/toDouble" => Dhall::Forall.of_arguments(
1182
- Dhall::Variable["Integer"],
1183
- body: Dhall::Variable["Double"]
1335
+ Builtins[:Integer],
1336
+ body: Builtins[:Double]
1184
1337
  ),
1185
1338
  "Double/show" => Dhall::Forall.of_arguments(
1186
- Dhall::Variable["Double"],
1187
- body: Dhall::Variable["Text"]
1339
+ Builtins[:Double],
1340
+ body: Builtins[:Text]
1188
1341
  )
1189
1342
  }.freeze
1190
1343
 
1191
1344
  class Builtin
1192
1345
  TypeChecker.register self, Dhall::Builtin
1193
1346
 
1347
+ def self.for(builtin)
1348
+ if builtin.is_a?(Dhall::BuiltinFunction)
1349
+ if (unfilled = builtin.unfill) != builtin
1350
+ return TypeChecker.for(unfilled)
1351
+ end
1352
+ end
1353
+
1354
+ new(builtin)
1355
+ end
1356
+
1194
1357
  def initialize(builtin)
1195
1358
  @expr = builtin
1196
1359
  @name = builtin.as_json