File: access_operator.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (719 lines) | stat: -rw-r--r-- 25,833 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
module Puppet::Pops
module Evaluator
# AccessOperator handles operator []
# This operator is part of evaluation.
#
class AccessOperator
  # Provides access to the Puppet 3.x runtime (scope, etc.)
  # This separation has been made to make it easier to later migrate the evaluator to an improved runtime.
  #
  include Runtime3Support

  attr_reader :semantic

  # Initialize with AccessExpression to enable reporting issues
  # @param access_expression [Model::AccessExpression] the semantic object being evaluated
  # @return [void]
  #
  def initialize(access_expression)
    @@access_visitor ||= Visitor.new(self, "access", 2, nil)
    @semantic = access_expression
  end

  def access(o, scope, *keys)
    @@access_visitor.visit_this_2(self, o, scope, keys)
  end

  protected

  def access_Object(o, scope, keys)
    type = Puppet::Pops::Types::TypeCalculator.infer_callable_methods_t(o)
    if type.is_a?(Puppet::Pops::Types::TypeWithMembers)
      access_func = type['[]']
      return access_func.invoke(o, scope, keys) unless access_func.nil?
    end
    fail(Issues::OPERATOR_NOT_APPLICABLE, @semantic.left_expr, :operator=>'[]', :left_value => o)
  end

  def access_Binary(o, scope, keys)
    Puppet::Pops::Types::PBinaryType::Binary.from_binary_string(access_String(o.binary_buffer, scope, keys))
  end

  def access_String(o, scope, keys)
    keys.flatten!
    result = case keys.size
    when 0
      fail(Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
    when 1
      # Note that Ruby 1.8.7 requires a length of 1 to produce a String
      k1 = Utils.to_n(keys[0])
      bad_string_access_key_type(o, 0, k1.nil? ? keys[0] : k1) unless k1.is_a?(Integer)
      k2 = 1
      k1 = k1 < 0 ? o.length + k1 : k1           # abs pos
      # if k1 is outside, a length of 1 always produces an empty string
      if k1 < 0
        EMPTY_STRING
      else
        o[ k1, k2 ]
      end
    when 2
      k1 = Utils.to_n(keys[0])
      k2 = Utils.to_n(keys[1])
      [k1, k2].each_with_index { |k,i| bad_string_access_key_type(o, i, k.nil? ? keys[i] : k) unless k.is_a?(Integer) }

      k1 = k1 < 0 ? o.length + k1 : k1           # abs pos (negative is count from end)
      k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2  # abs length (negative k2 is length from pos to end count)
      # if k1 is outside, adjust to first position, and adjust length
      if k1 < 0
        k2 = k2 + k1
        k1 = 0
      end
      o[ k1, k2 ]
    else
      fail(Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
    end
    # Specified as: an index outside of range, or empty result == empty string
    (result.nil? || result.empty?) ? EMPTY_STRING : result
  end

  # Parameterizes a PRegexp Type with a pattern string or r ruby egexp
  #
  def access_PRegexpType(o, scope, keys)
    keys.flatten!
    unless keys.size == 1
      blamed = keys.size == 0 ? @semantic : @semantic.keys[1]
      fail(Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :actual => keys.size)
    end
    assert_keys(keys, o, 1, 1, String, Regexp)
    Types::TypeFactory.regexp(*keys)
  end

  # Evaluates <ary>[] with 1 or 2 arguments. One argument is an index lookup, two arguments is a slice from/to.
  #
  def access_Array(o, scope, keys)
    keys.flatten!
    case keys.size
    when 0
      fail(Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
    when 1
      key = coerce_numeric(keys[0], @semantic.keys[0], scope)
      unless key.is_a?(Integer)
        bad_access_key_type(o, 0, key, Integer)
      end
      o[key]
    when 2
      # A slice [from, to] with support for -1 to mean start, or end respectively.
      k1 = coerce_numeric(keys[0], @semantic.keys[0], scope)
      k2 = coerce_numeric(keys[1], @semantic.keys[1], scope)

      [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) }

      # Help confused Ruby do the right thing (it truncates to the right, but negative index + length can never overlap
      # the available range.
      k1 = k1 < 0 ? o.length + k1 : k1           # abs pos (negative is count from end)
      k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2  # abs length (negative k2 is length from pos to end count)
      # if k1 is outside, adjust to first position, and adjust length
      if k1 < 0
        k2 = k2 + k1
        k1 = 0
      end
      # Help ruby always return empty array when asking for a sub array
      result = o[ k1, k2 ]
      result.nil? ? [] : result
    else
      fail(Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
    end
  end


  # Evaluates <hsh>[] with support for one or more arguments. If more than one argument is used, the result
  # is an array with each lookup.
  # @note
  #   Does not flatten its keys to enable looking up with a structure
  #
  def access_Hash(o, scope, keys)
    # Look up key in hash, if key is nil, try alternate form (:undef) before giving up.
    # This is done because the hash may have been produced by 3x logic and may thus contain :undef.
    result = keys.collect do |k|
      o.fetch(k) { |key| key.nil? ? o[:undef] : nil }
    end
    case result.size
    when 0
      fail(Issues::BAD_HASH_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
    when 1
      result.pop
    else
      # remove nil elements and return
      result.compact!
      result
    end
  end

  def access_PBooleanType(o, scope, keys)
    keys.flatten!
    assert_keys(keys, o, 1, 1, TrueClass, FalseClass)
    Types::TypeFactory.boolean(keys[0])
  end

  def access_PEnumType(o, scope, keys)
    keys.flatten!
    last = keys.last
    case_insensitive = false
    if last == true || last == false
      keys = keys[0...-1]
      case_insensitive = last
    end
    assert_keys(keys, o, 1, Float::INFINITY, String)
    Types::PEnumType.new(keys, case_insensitive)
  end

  def access_PVariantType(o, scope, keys)
    keys.flatten!
    assert_keys(keys, o, 1, Float::INFINITY, Types::PAnyType)
    Types::TypeFactory.variant(*keys)
  end

  def access_PSemVerType(o, scope, keys)
    keys.flatten!
    assert_keys(keys, o, 1, Float::INFINITY, String, SemanticPuppet::VersionRange)
    Types::TypeFactory.sem_ver(*keys)
  end

  def access_PTimestampType(o, scope, keys)
    keys.flatten!
    fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>0, :max => 2, :actual => keys.size) if keys.size > 2
    Types::TypeFactory.timestamp(*keys)
  end

  def access_PTimespanType(o, scope, keys)
    keys.flatten!
    fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>0, :max => 2, :actual => keys.size) if keys.size > 2
    Types::TypeFactory.timespan(*keys)
  end

  def access_PTupleType(o, scope, keys)
    keys.flatten!
    if Types::TypeFactory.is_range_parameter?(keys[-2]) && Types::TypeFactory.is_range_parameter?(keys[-1])
      size_type = Types::TypeFactory.range(keys[-2], keys[-1])
      keys = keys[0, keys.size - 2]
    elsif Types::TypeFactory.is_range_parameter?(keys[-1])
      size_type = Types::TypeFactory.range(keys[-1], :default)
      keys = keys[0, keys.size - 1]
    end
    assert_keys(keys, o, 1, Float::INFINITY, Types::PAnyType)
    Types::TypeFactory.tuple(keys, size_type)
  end

  def access_PCallableType(o, scope, keys)
    if keys.size > 0 && keys[0].is_a?(Array)
      unless keys.size == 2
        fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>2, :max => 2, :actual => keys.size)
      end
      unless keys[1].is_a?(Types::PAnyType)
        bad_type_specialization_key_type(o, 1, k, Types::PAnyType)
      end
    end
    Types::TypeFactory.callable(*keys)
  end

  def access_PStructType(o, scope, keys)
    assert_keys(keys, o, 1, 1, Hash)
    Types::TypeFactory.struct(keys[0])
  end

  def access_PStringType(o, scope, keys)
    keys.flatten!
    case keys.size
    when 1
      size_t = collection_size_t(0, keys[0])
    when 2
      size_t = collection_size_t(0, keys[0], keys[1])
    else
      fail(Issues::BAD_STRING_SLICE_ARITY, @semantic, {:actual => keys.size})
    end
    Types::TypeFactory.string(size_t)
  end

  # Asserts type of each key and calls fail with BAD_TYPE_SPECIFICATION
  # @param keys [Array<Object>] the evaluated keys
  # @param o [Object] evaluated LHS reported as :base_type
  # @param min [Integer] the minimum number of keys (typically 1)
  # @param max [Numeric] the maximum number of keys (use same as min, specific number, or Float::INFINITY)
  # @param allowed_classes [Class] a variable number of classes that each key must be an instance of (any)
  # @api private
  #
  def assert_keys(keys, o, min, max, *allowed_classes)
    size = keys.size
    unless size.between?(min, max || Float::INFINITY)
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>1, :max => max, :actual => keys.size)
    end
    keys.each_with_index do |k, i|
      unless allowed_classes.any? {|clazz| k.is_a?(clazz) }
        bad_type_specialization_key_type(o, i, k, *allowed_classes)
      end
    end
  end

  def bad_access_key_type(lhs, key_index, actual, *expected_classes)
    fail(Issues::BAD_SLICE_KEY_TYPE, @semantic.keys[key_index], {
      :left_value => lhs,
      :actual => bad_key_type_name(actual),
      :expected_classes => expected_classes
    })
  end

  def bad_string_access_key_type(lhs, key_index, actual)
    fail(Issues::BAD_STRING_SLICE_KEY_TYPE, @semantic.keys[key_index], {
      :left_value => lhs,
      :actual_type => bad_key_type_name(actual),
    })
  end

  def bad_key_type_name(actual)
    case actual
    when nil
      'Undef'
    when :default
      'Default'
    else
      Types::TypeCalculator.generalize(Types::TypeCalculator.infer(actual)).to_s
    end
  end

  def bad_type_specialization_key_type(type, key_index, actual, *expected_classes)
    label_provider = Model::ModelLabelProvider.new()
    expected = expected_classes.map {|c| label_provider.label(c) }.join(' or ')
    fail(Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[key_index], {
      :type => type,
      :message => _("Cannot use %{key} where %{expected} is expected") % { key: bad_key_type_name(actual), expected: expected }
    })
  end

  def access_PPatternType(o, scope, keys)
    keys.flatten!
    assert_keys(keys, o, 1, Float::INFINITY, String, Regexp, Types::PPatternType, Types::PRegexpType)
    Types::TypeFactory.pattern(*keys)
  end

  def access_PURIType(o, scope, keys)
    keys.flatten!
    if keys.size == 1
      param = keys[0]
      unless Types::PURIType::TYPE_URI_PARAM_TYPE.instance?(param)
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'URI-Type', :actual => param.class})
      end
      Types::PURIType.new(param)
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'URI-Type', :min => 1, :actual => keys.size})
    end
  end

  def access_POptionalType(o, scope, keys)
    keys.flatten!
    if keys.size == 1
      type = keys[0]
      unless type.is_a?(Types::PAnyType)
        if type.is_a?(String)
          type = Types::TypeFactory.string(type)
        else
          fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Optional-Type', :actual => type.class})
        end
      end
      Types::POptionalType.new(type)
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Optional-Type', :min => 1, :actual => keys.size})
    end
  end

  def access_PSensitiveType(o, scope, keys)
    keys.flatten!
    if keys.size == 1
      type = keys[0]
      unless type.is_a?(Types::PAnyType)
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Sensitive-Type', :actual => type.class})
      end
      Types::PSensitiveType.new(type)
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Sensitive-Type', :min => 1, :actual => keys.size})
    end
  end

  def access_PObjectType(o, scope, keys)
    keys.flatten!
    if o.resolved? && !o.name.nil?
      Types::PObjectTypeExtension.create(o, keys)
    else
      if keys.size == 1
        Types::TypeFactory.object(keys[0])
      else
        fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Object-Type', :min => 1, :actual => keys.size})
      end
    end
  end

  def access_PTypeSetType(o, scope, keys)
    keys.flatten!
    if keys.size == 1
      Types::TypeFactory.type_set(keys[0])
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'TypeSet-Type', :min => 1, :actual => keys.size})
    end
  end

  def access_PNotUndefType(o, scope, keys)
    keys.flatten!
    case keys.size
    when 0
      Types::TypeFactory.not_undef
    when 1
      type = keys[0]
      case type
      when String
        type = Types::TypeFactory.string(type)
      when Types::PAnyType
        type = nil if type.class == Types::PAnyType
      else
        fail(Issues::BAD_NOT_UNDEF_SLICE_TYPE, @semantic.keys[0], {:base_type => 'NotUndef-Type', :actual => type.class})
      end
      Types::TypeFactory.not_undef(type)
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'NotUndef-Type', :min => 0, :max => 1, :actual => keys.size})
    end
  end

  def access_PTypeType(o, scope, keys)
    keys.flatten!
    if keys.size == 1
      unless keys[0].is_a?(Types::PAnyType)
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Type-Type', :actual => keys[0].class})
      end
      Types::PTypeType.new(keys[0])
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Type-Type', :min => 1, :actual => keys.size})
    end
  end

  def access_PInitType(o, scope, keys)
    unless keys[0].is_a?(Types::PAnyType)
      fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Init-Type', :actual => keys[0].class})
    end
    Types::TypeFactory.init(*keys)
  end

  def access_PIterableType(o, scope, keys)
    keys.flatten!
    if keys.size == 1
      unless keys[0].is_a?(Types::PAnyType)
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Iterable-Type', :actual => keys[0].class})
      end
      Types::PIterableType.new(keys[0])
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Iterable-Type', :min => 1, :actual => keys.size})
    end
  end

  def access_PIteratorType(o, scope, keys)
    keys.flatten!
    if keys.size == 1
      unless keys[0].is_a?(Types::PAnyType)
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Iterator-Type', :actual => keys[0].class})
      end
      Types::PIteratorType.new(keys[0])
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Iterator-Type', :min => 1, :actual => keys.size})
    end
  end

  def access_PRuntimeType(o, scope, keys)
    keys.flatten!
    assert_keys(keys, o, 2, 2, String, String)
    # create runtime type based on runtime and name of class, (not inference of key's type)
    Types::TypeFactory.runtime(*keys)
  end

  def access_PIntegerType(o, scope, keys)
    keys.flatten!
    unless keys.size.between?(1, 2)
      fail(Issues::BAD_INTEGER_SLICE_ARITY, @semantic, {:actual => keys.size})
    end
    keys.each_with_index do |x, index|
      fail(Issues::BAD_INTEGER_SLICE_TYPE, @semantic.keys[index],
        {:actual => x.class}) unless (x.is_a?(Integer) || x == :default)
    end
    Types::PIntegerType.new(*keys)
  end

  def access_PFloatType(o, scope, keys)
    keys.flatten!
    unless keys.size.between?(1, 2)
      fail(Issues::BAD_FLOAT_SLICE_ARITY, @semantic, {:actual => keys.size})
    end
    keys.each_with_index do |x, index|
      fail(Issues::BAD_FLOAT_SLICE_TYPE, @semantic.keys[index],
        {:actual => x.class}) unless (x.is_a?(Float) || x.is_a?(Integer) || x == :default)
    end
    from, to = keys
    from = from == :default || from.nil? ? nil : Float(from)
    to = to == :default || to.nil? ? nil : Float(to)
    Types::PFloatType.new(from, to)
  end

  # A Hash can create a new Hash type, one arg sets value type, two args sets key and value type in new type.
  # With 3 or 4 arguments, these are used to create a size constraint.
  # It is not possible to create a collection of Hash types directly.
  #
  def access_PHashType(o, scope, keys)
    keys.flatten!
    if keys.size == 2 && keys[0].is_a?(Integer) && keys[1].is_a?(Integer)
      return Types::PHashType.new(nil, nil, Types::PIntegerType.new(*keys))
    end

    keys[0,2].each_with_index do |k, index|
      unless k.is_a?(Types::PAnyType)
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class})
      end
    end
    case keys.size
    when 2
      size_t = nil
    when 3
      size_t = keys[2]
      size_t = Types::PIntegerType.new(size_t) unless size_t.is_a?(Types::PIntegerType)
    when 4
      size_t = collection_size_t(2, keys[2], keys[3])
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {
        :base_type => 'Hash-Type', :min => 2, :max => 4, :actual => keys.size
      })
    end
    Types::PHashType.new(keys[0], keys[1], size_t)
  end

  # CollectionType is parameterized with a range
  def access_PCollectionType(o, scope, keys)
    keys.flatten!
    case keys.size
    when 1
      size_t = collection_size_t(0, keys[0])
    when 2
      size_t = collection_size_t(0, keys[0], keys[1])
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic,
        {:base_type => 'Collection-Type', :min => 1, :max => 2, :actual => keys.size})
    end
    Types::PCollectionType.new(size_t)
  end

  # An Array can create a new Array type. It is not possible to create a collection of Array types.
  #
  def access_PArrayType(o, scope, keys)
    keys.flatten!
    case keys.size
    when 1
      unless keys[0].is_a?(Types::PAnyType)
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class})
      end
      type = keys[0]
      size_t = nil
    when 2
      if keys[0].is_a?(Types::PAnyType)
        size_t = collection_size_t(1, keys[1])
        type = keys[0]
      else
        size_t = collection_size_t(0, keys[0], keys[1])
        type = nil
      end
    when 3
      if keys[0].is_a?(Types::PAnyType)
        size_t = collection_size_t(1, keys[1], keys[2])
        type = keys[0]
      else
        fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class})
      end
    else
      fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic,
        {:base_type => 'Array-Type', :min => 1, :max => 3, :actual => keys.size})
    end
    Types::PArrayType.new(type, size_t)
  end

  # Produces an PIntegerType (range) given one or two keys.
  def collection_size_t(start_index, *keys)
    if keys.size == 1 && keys[0].is_a?(Types::PIntegerType)
      keys[0]
    else
      keys.each_with_index do |x, index|
        fail(Issues::BAD_COLLECTION_SLICE_TYPE, @semantic.keys[start_index + index],
          {:actual => x.class}) unless (x.is_a?(Integer) || x == :default)
      end
      Types::PIntegerType.new(*keys)
    end
  end

  # A Puppet::Resource represents either just a type (no title), or is a fully qualified type/title.
  #
  def access_Resource(o, scope, keys)
    # To access a Puppet::Resource as if it was a PResourceType, simply infer it, and take the type of
    # the parameterized meta type (i.e. Type[Resource[the_resource_type, the_resource_title]])
    t = Types::TypeCalculator.infer(o).type
    # must map "undefined title" from resource to nil
    t.title = nil if t.title == EMPTY_STRING
    access(t, scope, *keys)
  end

  # If a type reference is encountered here, it's an error
  def access_PTypeReferenceType(o, scope, keys)
    fail(Issues::UNKNOWN_RESOURCE_TYPE, @semantic, {:type_name => o.type_string })
  end

  # A Resource can create a new more specific Resource type, and/or an array of resource types
  # If the given type has title set, it can not be specified further.
  # @example
  #   Resource[File]               # => File
  #   Resource[File, 'foo']        # => File[foo]
  #   Resource[File. 'foo', 'bar'] # => [File[foo], File[bar]]
  #   File['foo', 'bar']           # => [File[foo], File[bar]]
  #   File['foo']['bar']           # => Value of the 'bar' parameter in the File['foo'] resource
  #   Resource[File]['foo', 'bar'] # => [File[Foo], File[bar]]
  #   Resource[File, 'foo', 'bar'] # => [File[foo], File[bar]]
  #   Resource[File, 'foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource
  #
  def access_PResourceType(o, scope, keys)
    blamed = keys.size == 0 ? @semantic : @semantic.keys[0]

    if keys.size == 0
      fail(Issues::BAD_TYPE_SLICE_ARITY, blamed,
        :base_type => o.to_s, :min => 1, :max => -1, :actual => 0)
    end

    # Must know which concrete resource type to operate on in all cases.
    # It is not allowed to specify the type in an array arg - e.g. Resource[[File, 'foo']]
    # type_name is LHS type_name if set, else the first given arg
    type_name = o.type_name || Types::TypeFormatter.singleton.capitalize_segments(keys.shift)
    type_name = case type_name
    when Types::PResourceType
      type_name.type_name
    when String
      type_name
    else
      # blame given left expression if it defined the type, else the first given key expression
      blame = o.type_name.nil? ? @semantic.keys[0] : @semantic.left_expr
      fail(Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => bad_key_type_name(type_name)})
    end

    # type name must conform
    if type_name !~ Patterns::CLASSREF_EXT
      fail(Issues::ILLEGAL_CLASSREF, blamed, {:name=>type_name})
    end

    # The result is an array if multiple titles are given, or if titles are specified with an array
    # (possibly multiple arrays, and nested arrays).
    result_type_array = keys.size > 1 || keys[0].is_a?(Array)
    keys_orig_size = keys.size

    keys.flatten!
    keys.compact!

    # If given keys  that were just a mix of empty/nil with empty array as a result.
    # As opposed to calling the function the wrong way (without any arguments), (configurable issue),
    # Return an empty array
    #
    if keys.empty? && keys_orig_size > 0
      optionally_fail(Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed)
      return result_type_array ? [] : nil
    end

    if !o.title.nil?
      # lookup resource and return one or more parameter values
      resource = find_resource(scope, o.type_name, o.title)
      unless resource
        fail(Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => o.type_name, :title => o.title})
      end

      result = keys.map do |k|
        unless is_parameter_of_resource?(scope, resource, k)
          fail(Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic,
            {:type_name => o.type_name, :title => o.title, :param_name=>k})
        end
        get_resource_parameter_value(scope, resource, k)
      end
      return result_type_array ? result : result.pop
    end


    keys = [:no_title] if keys.size < 1 # if there was only a type_name and it was consumed
    result = keys.each_with_index.map do |t, i|
      unless t.is_a?(String) || t == :no_title
        index = keys_orig_size != keys.size ? i+1 : i
        fail(Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[index], {
          :type => o,
          :message => "Cannot use #{bad_key_type_name(t)} where a resource title String is expected"
        })
      end

      Types::PResourceType.new(type_name, t == :no_title ? nil : t)
    end
    # returns single type if request was for a single entity, else an array of types (possibly empty)
    return result_type_array ? result : result.pop
  end

  NS = '::'.freeze

  def access_PClassType(o, scope, keys)
    blamed = keys.size == 0 ? @semantic : @semantic.keys[0]
    keys_orig_size = keys.size

    if keys_orig_size == 0
      fail(Issues::BAD_TYPE_SLICE_ARITY, blamed,
        :base_type => o.to_s, :min => 1, :max => -1, :actual => 0)
    end

    # The result is an array if multiple classnames are given, or if classnames are specified with an array
    # (possibly multiple arrays, and nested arrays).
    result_type_array = keys.size > 1 || keys[0].is_a?(Array)

    keys.flatten!
    keys.compact!

    # If given keys  that were just a mix of empty/nil with empty array as a result.
    # As opposed to calling the function the wrong way (without any arguments), (configurable issue),
    # Return an empty array
    #
    if keys.empty? && keys_orig_size > 0
      optionally_fail(Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed)
      return result_type_array ? [] : nil
    end

    if o.class_name.nil?
      result = keys.each_with_index.map do |c, i|
        fail(Issues::ILLEGAL_HOSTCLASS_NAME, @semantic.keys[i], {:name => c}) unless c.is_a?(String)
        name = c.downcase
        # Remove leading '::' since all references are global, and 3x runtime does the wrong thing
        name = name[2..-1] if name[0,2] == NS

        fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c}) unless name =~ Patterns::NAME
        Types::PClassType.new(name)
      end
    else
      # lookup class resource and return one or more parameter values
      resource = find_resource(scope, 'class', o.class_name)
      if resource
        result = keys.map do |k|
          if is_parameter_of_resource?(scope, resource, k)
            get_resource_parameter_value(scope, resource, k)
          else
            fail(Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic,
              {:type_name => 'Class', :title => o.class_name, :param_name=>k})
          end
        end
      else
        fail(Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => 'Class', :title => o.class_name})
      end
    end

    # returns single type as type, else an array of types
    return result_type_array ? result : result.pop
  end
end
end
end