Method: Puppet::Pops::Types::TypeCalculator#common_type

Defined in:
lib/puppet/pops/types/type_calculator.rb

#common_type(t1, t2) ⇒ Object

Answers, ‘What is the common type of t1 and t2?’

TODO: The current implementation should be optimized for performance

Raises:

  • (ArgumentError)


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
# File 'lib/puppet/pops/types/type_calculator.rb', line 330

def common_type(t1, t2)
  raise ArgumentError, 'two types expected' unless (is_ptype?(t1) || is_pnil?(t1)) && (is_ptype?(t2) || is_pnil?(t2))

  # TODO: This is not right since Scalar U Undef is Any
  # if either is nil, the common type is the other
  if is_pnil?(t1)
    return t2
  elsif is_pnil?(t2)
    return t1
  end

  # If either side is Unit, it is the other type
  if t1.is_a?(PUnitType)
    return t2
  elsif t2.is_a?(PUnitType)
    return t1
  end

  # Simple case, one is assignable to the other
  if assignable?(t1, t2)
    return t1
  elsif assignable?(t2, t1)
    return t2
  end

  # when both are arrays, return an array with common element type
  if t1.is_a?(PArrayType) && t2.is_a?(PArrayType)
    return PArrayType.new(common_type(t1.element_type, t2.element_type))
  end

  # when both are hashes, return a hash with common key- and element type
  if t1.is_a?(PHashType) && t2.is_a?(PHashType)
    key_type = common_type(t1.key_type, t2.key_type)
    value_type = common_type(t1.value_type, t2.value_type)
    return PHashType.new(key_type, value_type)
  end

  # when both are host-classes, reduce to PHostClass[] (since one was not assignable to the other)
  if t1.is_a?(PClassType) && t2.is_a?(PClassType)
    return PClassType::DEFAULT
  end

  # when both are resources, reduce to Resource[T] or Resource[] (since one was not assignable to the other)
  if t1.is_a?(PResourceType) && t2.is_a?(PResourceType)
    # only Resource[] unless the type name is the same
    return t1.type_name == t2.type_name ? PResourceType.new(t1.type_name, nil) : PResourceType::DEFAULT
  end

  # Integers have range, expand the range to the common range
  if t1.is_a?(PIntegerType) && t2.is_a?(PIntegerType)
    return PIntegerType.new([t1.numeric_from, t2.numeric_from].min, [t1.numeric_to, t2.numeric_to].max)
  end

  # Floats have range, expand the range to the common range
  if t1.is_a?(PFloatType) && t2.is_a?(PFloatType)
    return PFloatType.new([t1.numeric_from, t2.numeric_from].min, [t1.numeric_to, t2.numeric_to].max)
  end

  if t1.is_a?(PStringType) && (t2.is_a?(PStringType) || t2.is_a?(PEnumType))
    if t2.is_a?(PEnumType)
      return t1.value.nil? ? PEnumType::DEFAULT : PEnumType.new(t2.values | [t1.value])
    end

    if t1.size_type.nil? || t2.size_type.nil?
      return t1.value.nil? || t2.value.nil? ? PStringType::DEFAULT : PEnumType.new([t1.value, t2.value])
    end

    return PStringType.new(common_type(t1.size_type, t2.size_type))
  end

  if t1.is_a?(PPatternType) && t2.is_a?(PPatternType)
    return PPatternType.new(t1.patterns | t2.patterns)
  end

  if t1.is_a?(PEnumType) && (t2.is_a?(PStringType) || t2.is_a?(PEnumType))
    # The common type is one that complies with either set
    if t2.is_a?(PEnumType)
      return PEnumType.new(t1.values | t2.values)
    end

    return t2.value.nil? ? PEnumType::DEFAULT : PEnumType.new(t1.values | [t2.value])
  end

  if t1.is_a?(PVariantType) && t2.is_a?(PVariantType)
    # The common type is one that complies with either set
    return PVariantType.maybe_create(t1.types | t2.types)
  end

  if t1.is_a?(PRegexpType) && t2.is_a?(PRegexpType)
    # if they were identical, the general rule would return a parameterized regexp
    # since they were not, the result is a generic regexp type
    return PRegexpType::DEFAULT
  end

  if t1.is_a?(PCallableType) && t2.is_a?(PCallableType)
    # They do not have the same signature, and one is not assignable to the other,
    # what remains is the most general form of Callable
    return PCallableType::DEFAULT
  end

  # Common abstract types, from most specific to most general
  if common_numeric?(t1, t2)
    return PNumericType::DEFAULT
  end

  if common_scalar_data?(t1, t2)
    return PScalarDataType::DEFAULT
  end

  if common_scalar?(t1, t2)
    return PScalarType::DEFAULT
  end

  if common_data?(t1, t2)
    return TypeFactory.data
  end

  # Meta types Type[Integer] + Type[String] => Type[Data]
  if t1.is_a?(PTypeType) && t2.is_a?(PTypeType)
    return PTypeType.new(common_type(t1.type, t2.type))
  end

  if common_rich_data?(t1, t2)
    return TypeFactory.rich_data
  end

  # If both are Runtime types
  if t1.is_a?(PRuntimeType) && t2.is_a?(PRuntimeType)
    if t1.runtime == t2.runtime && t1.runtime_type_name == t2.runtime_type_name
      return t1
    end

    # finding the common super class requires that names are resolved to class
    # NOTE: This only supports runtime type of :ruby
    c1 = ClassLoader.provide_from_type(t1)
    c2 = ClassLoader.provide_from_type(t2)
    if c1 && c2
      c2_superclasses = superclasses(c2)
      superclasses(c1).each do |c1_super|
        c2_superclasses.each do |c2_super|
          if c1_super == c2_super
            return PRuntimeType.new(:ruby, c1_super.name)
          end
        end
      end
    end
  end

  # They better both be Any type, or the wrong thing was asked and nil is returned
  t1.is_a?(PAnyType) && t2.is_a?(PAnyType) ? PAnyType::DEFAULT : nil
end