From 7c8ae311cade65e84df1054779a039ff906e630c Mon Sep 17 00:00:00 2001
From: Hunter Haugen <hunter@puppetlabs.com>
Date: Mon, 15 Dec 2014 16:11:10 -0800
Subject: [PATCH] (MODULES-1473) Deprecate type() function for new parser

The `type()` function will cease to work on the new parser because 'type'
is a reserved keyword. The `type3x()` function may be used to continue
similar functionality, but will be deprecated in favor of the built-in
typing system.

The `type_of()` function has been included to introspect types in the
new parser.
---
 .fixtures.yml                              |  3 ++
 .travis.yml                                |  1 +
 README.markdown                            |  7 ++-
 lib/puppet/functions/type_of.rb            | 17 ++++++++
 lib/puppet/parser/functions/type.rb        | 43 +++---------------
 lib/puppet/parser/functions/type3x.rb      | 51 ++++++++++++++++++++++
 spec/functions/type3x_spec.rb              | 43 ++++++++++++++++++
 spec/functions/type_spec.rb                |  5 ++-
 spec/unit/puppet/functions/type_of_spec.rb | 33 ++++++++++++++
 9 files changed, 163 insertions(+), 40 deletions(-)
 create mode 100644 .fixtures.yml
 create mode 100644 lib/puppet/functions/type_of.rb
 create mode 100644 lib/puppet/parser/functions/type3x.rb
 create mode 100644 spec/functions/type3x_spec.rb
 create mode 100644 spec/unit/puppet/functions/type_of_spec.rb

diff --git a/.fixtures.yml b/.fixtures.yml
new file mode 100644
index 000000000..37b737752
--- /dev/null
+++ b/.fixtures.yml
@@ -0,0 +1,3 @@
+fixtures:
+  symlinks:
+    stdlib: "#{source_dir}"
diff --git a/.travis.yml b/.travis.yml
index f531306e7..503e1844d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,5 @@
 ---
+sudo: false
 language: ruby
 bundler_args: --without system_tests
 script: "bundle exec rake validate && bundle exec rake lint && bundle exec rake spec SPEC_OPTS='--color --format documentation'"
diff --git a/README.markdown b/README.markdown
index 689660eca..e9a1f4e4f 100644
--- a/README.markdown
+++ b/README.markdown
@@ -464,7 +464,12 @@ manifests as a valid password attribute. *Type*: rvalue
 * `to_bytes`: Converts the argument into bytes, for example 4 kB becomes 4096.
 Takes a single string value as an argument. *Type*: rvalue
 
-* `type`: Returns the type when passed a variable. Type can be a string, array, hash, float, integer, or boolean. *Type*: rvalue
+* `type3x`: Returns a string description of the type when passed a value. Type can be a string, array, hash, float, integer, or boolean. This function will be removed when puppet 3 support is dropped and the new type system may be used. *Type*: rvalue
+
+* `type_of`: Returns the literal type when passed a value. Requires the new
+  parser. Useful for comparison of types with `<=` such as in `if
+  type_of($some_value) <= Array[String] { ... }` (which is equivalent to `if
+  $some_value =~ Array[String] { ... }`) *Type*: rvalue
 
 * `union`: This function returns a union of two arrays. For example, `union(["a","b","c"],["b","c","d"])` returns ["a","b","c","d"].
 
diff --git a/lib/puppet/functions/type_of.rb b/lib/puppet/functions/type_of.rb
new file mode 100644
index 000000000..02cdd4db7
--- /dev/null
+++ b/lib/puppet/functions/type_of.rb
@@ -0,0 +1,17 @@
+# Returns the type when passed a value.
+#
+# @example how to compare values' types
+#   # compare the types of two values
+#   if type_of($first_value) != type_of($second_value) { fail("first_value and second_value are different types") }
+# @example how to compare against an abstract type
+#   unless type_of($first_value) <= Numeric { fail("first_value must be Numeric") }
+#   unless type_of{$first_value) <= Collection[1] { fail("first_value must be an Array or Hash, and contain at least one element") }
+#
+# See the documentation for "The Puppet Type System" for more information about types.
+# See the `assert_type()` function for flexible ways to assert the type of a value.
+#
+Puppet::Functions.create_function(:type_of) do
+  def type_of(value)
+    Puppet::Pops::Types::TypeCalculator.infer_set(value)
+  end
+end
diff --git a/lib/puppet/parser/functions/type.rb b/lib/puppet/parser/functions/type.rb
index 8d85f1158..016529b03 100644
--- a/lib/puppet/parser/functions/type.rb
+++ b/lib/puppet/parser/functions/type.rb
@@ -4,46 +4,15 @@
 
 module Puppet::Parser::Functions
   newfunction(:type, :type => :rvalue, :doc => <<-EOS
-Returns the type when passed a variable. Type can be one of:
-
-* string
-* array
-* hash
-* float
-* integer
-* boolean
+  DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.
     EOS
-  ) do |arguments|
-
-    raise(Puppet::ParseError, "type(): Wrong number of arguments " +
-      "given (#{arguments.size} for 1)") if arguments.size < 1
-
-    value = arguments[0]
-
-    klass = value.class
-
-    if not [TrueClass, FalseClass, Array, Bignum, Fixnum, Float, Hash, String].include?(klass)
-      raise(Puppet::ParseError, 'type(): Unknown type')
-    end
-
-    klass = klass.to_s # Ugly ...
+  ) do |args|
 
-    # We note that Integer is the parent to Bignum and Fixnum ...
-    result = case klass
-      when /^(?:Big|Fix)num$/ then 'integer'
-      when /^(?:True|False)Class$/ then 'boolean'
-      else klass
+    warning("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.")
+    if ! Puppet::Parser::Functions.autoloader.loaded?(:type3x)
+      Puppet::Parser::Functions.autoloader.load(:type3x)
     end
-
-    if result == "String" then
-      if value == value.to_i.to_s then
-        result = "Integer"
-      elsif value == value.to_f.to_s then
-        result = "Float"
-      end
-    end
-
-    return result.downcase
+    function_type3x(args + [false])
   end
 end
 
diff --git a/lib/puppet/parser/functions/type3x.rb b/lib/puppet/parser/functions/type3x.rb
new file mode 100644
index 000000000..0800b4a3e
--- /dev/null
+++ b/lib/puppet/parser/functions/type3x.rb
@@ -0,0 +1,51 @@
+#
+# type3x.rb
+#
+
+module Puppet::Parser::Functions
+  newfunction(:type3x, :type => :rvalue, :doc => <<-EOS
+DEPRECATED: This function will be removed when puppet 3 support is dropped; please migrate to the new parser's typing system.
+
+Returns the type when passed a value. Type can be one of:
+
+* string
+* array
+* hash
+* float
+* integer
+* boolean
+    EOS
+  ) do |args|
+    raise(Puppet::ParseError, "type3x(): Wrong number of arguments " +
+      "given (#{args.size} for 1)") if args.size < 1
+
+    value = args[0]
+
+    klass = value.class
+
+    if not [TrueClass, FalseClass, Array, Bignum, Fixnum, Float, Hash, String].include?(klass)
+      raise(Puppet::ParseError, 'type3x(): Unknown type')
+    end
+
+    klass = klass.to_s # Ugly ...
+
+    # We note that Integer is the parent to Bignum and Fixnum ...
+    result = case klass
+      when /^(?:Big|Fix)num$/ then 'integer'
+      when /^(?:True|False)Class$/ then 'boolean'
+      else klass
+    end
+
+    if result == "String" then
+      if value == value.to_i.to_s then
+        result = "Integer"
+      elsif value == value.to_f.to_s then
+        result = "Float"
+      end
+    end
+
+    return result.downcase
+  end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/spec/functions/type3x_spec.rb b/spec/functions/type3x_spec.rb
new file mode 100644
index 000000000..d21236a61
--- /dev/null
+++ b/spec/functions/type3x_spec.rb
@@ -0,0 +1,43 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the type3x function" do
+  let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+  it "should exist" do
+    expect(Puppet::Parser::Functions.function("type3x")).to eq("function_type3x")
+  end
+
+  it "should raise a ParseError if there is less than 1 arguments" do
+    expect { scope.function_type3x([]) }.to( raise_error(Puppet::ParseError))
+  end
+
+  it "should return string when given a string" do
+    result = scope.function_type3x(["aaabbbbcccc"])
+    expect(result).to(eq('string'))
+  end
+
+  it "should return array when given an array" do
+    result = scope.function_type3x([["aaabbbbcccc","asdf"]])
+    expect(result).to(eq('array'))
+  end
+
+  it "should return hash when given a hash" do
+    result = scope.function_type3x([{"a"=>1,"b"=>2}])
+    expect(result).to(eq('hash'))
+  end
+
+  it "should return integer when given an integer" do
+    result = scope.function_type3x(["1"])
+    expect(result).to(eq('integer'))
+  end
+
+  it "should return float when given a float" do
+    result = scope.function_type3x(["1.34"])
+    expect(result).to(eq('float'))
+  end
+
+  it "should return boolean when given a boolean" do
+    result = scope.function_type3x([true])
+    expect(result).to(eq('boolean'))
+  end
+end
diff --git a/spec/functions/type_spec.rb b/spec/functions/type_spec.rb
index 9dfe9d7f5..b683fcfa4 100755
--- a/spec/functions/type_spec.rb
+++ b/spec/functions/type_spec.rb
@@ -7,8 +7,9 @@
     expect(Puppet::Parser::Functions.function("type")).to eq("function_type")
   end
 
-  it "should raise a ParseError if there is less than 1 arguments" do
-    expect { scope.function_type([]) }.to( raise_error(Puppet::ParseError))
+  it "should give a deprecation warning when called" do
+    scope.expects(:warning).with("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.")
+    scope.function_type(["aoeu"])
   end
 
   it "should return string when given a string" do
diff --git a/spec/unit/puppet/functions/type_of_spec.rb b/spec/unit/puppet/functions/type_of_spec.rb
new file mode 100644
index 000000000..8afb62464
--- /dev/null
+++ b/spec/unit/puppet/functions/type_of_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+if ENV["FUTURE_PARSER"] == 'yes' or Puppet.version >= "4"
+  require 'puppet/pops'
+  require 'puppet/loaders'
+
+  describe 'the type_of function' do
+    before(:all) do
+      loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [File.join(fixtures, "modules")]))
+      Puppet.push_context({:loaders => loaders}, "test-examples")
+    end
+
+    after(:all) do
+      Puppet::Pops::Loaders.clear
+      Puppet::pop_context()
+    end
+
+    let(:func) do
+      # Load the function from the environment modulepath's modules (ie, fixtures)
+      Puppet.lookup(:loaders).private_environment_loader.load(:function, 'type_of')
+    end
+
+    it 'gives the type of a string' do
+      expect(func.call({}, 'hello world')).to be_kind_of(Puppet::Pops::Types::PStringType)
+    end
+
+    it 'gives the type of an integer' do
+      expect(func.call({}, 5)).to be_kind_of(Puppet::Pops::Types::PIntegerType)
+    end
+  end
+end