From 771457fb3f33c264aa92430d486007416c796b9c Mon Sep 17 00:00:00 2001 From: Simon George Date: Thu, 26 Nov 2015 00:47:05 +0000 Subject: [PATCH 1/2] Add Int and NatPos builtin contracts --- TUTORIAL.md | 2 + features/builtin_contracts/int.feature | 93 ++++++++++++++++ features/builtin_contracts/nat_pos.feature | 119 +++++++++++++++++++++ lib/contracts/builtin_contracts.rb | 16 ++- 4 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 features/builtin_contracts/int.feature create mode 100644 features/builtin_contracts/nat_pos.feature diff --git a/TUTORIAL.md b/TUTORIAL.md index de4a341..66e3ac9 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -71,7 +71,9 @@ contracts.ruby comes with a lot of built-in contracts, including the following: * [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Num) – checks that the argument is `Numeric` * [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Pos) – checks that the argument is a positive number * [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Neg) – checks that the argument is a negative number + * [`Int`](http://www.rubydoc.info/gems/contracts/Contracts/Int) – checks that the argument is an integer * [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Nat) – checks that the argument is a natural number (>= 0) + * [`NatPos`](http://www.rubydoc.info/gems/contracts/Contracts/NatPos) – checks that the argument is a positive natural number (> 0) * [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Bool) – checks that the argument is `true` or `false` * [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Any) – Passes for any argument. Use when the argument has no constraints. * [`None`](http://www.rubydoc.info/gems/contracts/Contracts/None) – Fails for any argument. Use when the method takes no arguments. diff --git a/features/builtin_contracts/int.feature b/features/builtin_contracts/int.feature new file mode 100644 index 0000000..54bfc80 --- /dev/null +++ b/features/builtin_contracts/int.feature @@ -0,0 +1,93 @@ +Feature: Int + + Checks that an argument is an integer. + + ```ruby + Contract C::Int => C::Int + ``` + + Background: + Given a file named "int_usage.rb" with: + """ruby + require "contracts" + C = Contracts + + class Integr + include Contracts::Core + + Contract C::Int => C::Int + def prev(number) + number - 1 + end + end + """ + + Scenario: Accepts positive integers + Given a file named "accepts_positive_integers.rb" with: + """ruby + require "./int_usage" + puts Integr.new.prev(7) + """ + When I run `ruby accepts_positive_integers.rb` + Then output should contain: + """ + 6 + """ + + Scenario: Accepts zero + Given a file named "accepts_zero.rb" with: + """ruby + require "./int_usage" + puts Integr.new.prev(1) + """ + When I run `ruby accepts_zero.rb` + Then output should contain: + """ + 0 + """ + + Scenario: Accepts negative integers + Given a file named "accepts_negative_integers.rb" with: + """ruby + require "./int_usage" + puts Integr.new.prev(-1) + """ + When I run `ruby accepts_negative_integers.rb` + Then output should contain: + """ + -2 + """ + + Scenario: Rejects floats + Given a file named "rejects_floats.rb" with: + """ruby + require "./int_usage" + puts Integr.new.prev(3.43) + """ + When I run `ruby rejects_floats.rb` + Then output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: Int, + Actual: 3.43 + Value guarded in: Integr::prev + With Contract: Int => Int + """ + And output should contain "int_usage.rb:8" + + Scenario: Rejects other values + Given a file named "rejects_others.rb" with: + """ruby + require "./int_usage" + puts Integr.new.prev("foo") + """ + When I run `ruby rejects_others.rb` + Then output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: Int, + Actual: "foo" + Value guarded in: Integr::prev + With Contract: Int => Int + """ + And output should contain "int_usage.rb:8" diff --git a/features/builtin_contracts/nat_pos.feature b/features/builtin_contracts/nat_pos.feature new file mode 100644 index 0000000..4a33ca9 --- /dev/null +++ b/features/builtin_contracts/nat_pos.feature @@ -0,0 +1,119 @@ +Feature: NatPos + + Checks that an argument is a positive natural number. + + ```ruby + Contract C::NatPos => C::NatPos + ``` + + Background: + Given a file named "nat_pos_usage.rb" with: + """ruby + require "contracts" + C = Contracts + + class NaturalPositive + include Contracts::Core + + Contract C::NatPos => C::NatPos + def prev(number) + number - 1 + end + end + """ + + Scenario: Accepts positive integers + Given a file named "accepts_positive_integers.rb" with: + """ruby + require "./nat_pos_usage" + puts NaturalPositive.new.prev(7) + """ + When I run `ruby accepts_positive_integers.rb` + Then output should contain: + """ + 6 + """ + + Scenario: Rejects zero + Given a file named "rejects_zero.rb" with: + """ruby + require "./nat_pos_usage" + puts NaturalPositive.new.prev(0) + """ + When I run `ruby rejects_zero.rb` + Then output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: NatPos, + Actual: 0 + Value guarded in: NaturalPositive::prev + With Contract: NatPos => NatPos + """ + + Scenario: Rejects negative integers + Given a file named "rejects_negative_integers.rb" with: + """ruby + require "./nat_pos_usage" + puts NaturalPositive.new.prev(-1) + """ + When I run `ruby rejects_negative_integers.rb` + Then output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: NatPos, + Actual: -1 + Value guarded in: NaturalPositive::prev + With Contract: NatPos => NatPos + """ + And output should contain "nat_pos_usage.rb:8" + + Scenario: Rejects negative integers as a return value + Given a file named "rejects_negative_integers.rb" with: + """ruby + require "./nat_pos_usage" + puts NaturalPositive.new.prev(1) + """ + When I run `ruby rejects_negative_integers.rb` + Then output should contain: + """ + : Contract violation for return value: (ReturnContractError) + Expected: NatPos, + Actual: 0 + Value guarded in: NaturalPositive::prev + With Contract: NatPos => NatPos + """ + And output should contain "nat_pos_usage.rb:8" + + Scenario: Rejects floats + Given a file named "rejects_floats.rb" with: + """ruby + require "./nat_pos_usage" + puts NaturalPositive.new.prev(3.43) + """ + When I run `ruby rejects_floats.rb` + Then output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: NatPos, + Actual: 3.43 + Value guarded in: NaturalPositive::prev + With Contract: NatPos => NatPos + """ + And output should contain "nat_pos_usage.rb:8" + + Scenario: Rejects other values + Given a file named "rejects_others.rb" with: + """ruby + require "./nat_pos_usage" + puts NaturalPositive.new.prev("foo") + """ + When I run `ruby rejects_others.rb` + Then output should contain: + """ + : Contract violation for argument 1 of 1: (ParamContractError) + Expected: NatPos, + Actual: "foo" + Value guarded in: NaturalPositive::prev + With Contract: NatPos => NatPos + """ + And output should contain "nat_pos_usage.rb:8" diff --git a/lib/contracts/builtin_contracts.rb b/lib/contracts/builtin_contracts.rb index 1430d6a..4695758 100644 --- a/lib/contracts/builtin_contracts.rb +++ b/lib/contracts/builtin_contracts.rb @@ -41,13 +41,27 @@ def self.valid? val end end - # Check that an argument is a natural number. + # Check that an argument is an +Integer+. + class Int + def self.valid? val + val && val.is_a?(Integer) + end + end + + # Check that an argument is a natural number (includes zero). class Nat def self.valid? val val && val.is_a?(Integer) && val >= 0 end end + # Check that an argument is a positive natural number (excludes zero). + class NatPos + def self.valid? val + val && val.is_a?(Integer) && val > 0 + end + end + # Passes for any argument. class Any def self.valid? val From e7ccfb6a01b09df679740b7f9846d4fa31a6f396 Mon Sep 17 00:00:00 2001 From: Simon George Date: Thu, 26 Nov 2015 00:49:57 +0000 Subject: [PATCH 2/2] Fix tutorial builtin namespacing --- TUTORIAL.md | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 66e3ac9..1c8a9d2 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -68,44 +68,44 @@ This can be useful if you're in a REPL and want to figure out how a function sho contracts.ruby comes with a lot of built-in contracts, including the following: * Basic types - * [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Num) – checks that the argument is `Numeric` - * [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Pos) – checks that the argument is a positive number - * [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Neg) – checks that the argument is a negative number - * [`Int`](http://www.rubydoc.info/gems/contracts/Contracts/Int) – checks that the argument is an integer - * [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Nat) – checks that the argument is a natural number (>= 0) - * [`NatPos`](http://www.rubydoc.info/gems/contracts/Contracts/NatPos) – checks that the argument is a positive natural number (> 0) - * [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Bool) – checks that the argument is `true` or `false` - * [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Any) – Passes for any argument. Use when the argument has no constraints. - * [`None`](http://www.rubydoc.info/gems/contracts/Contracts/None) – Fails for any argument. Use when the method takes no arguments. + * [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Num) – checks that the argument is `Numeric` + * [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Pos) – checks that the argument is a positive number + * [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Neg) – checks that the argument is a negative number + * [`Int`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Int) – checks that the argument is an integer + * [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Nat) – checks that the argument is a natural number (>= 0) + * [`NatPos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/NatPos) – checks that the argument is a positive natural number (> 0) + * [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Bool) – checks that the argument is `true` or `false` + * [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Any) – Passes for any argument. Use when the argument has no constraints. + * [`None`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/None) – Fails for any argument. Use when the method takes no arguments. * Logical combinations - * [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`) - * [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]` - * [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]` - * [`And`](http://www.rubydoc.info/gems/contracts/Contracts/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]` - * [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]` + * [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`) + * [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]` + * [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]` + * [`And`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]` + * [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]` * Collections - * [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]` - * [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]` - * [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]` - * [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]` - * [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]` + * [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]` + * [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]` + * [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]` + * [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]` + * [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]` * Keyword arguments - * [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/KeywordArgs) – checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts, e.g. `KeywordArgs[:number => Num, :description => Optional[String]]` - * [`Optional`](http://www.rubydoc.info/gems/contracts/Contracts/Optional) – checks that the keyword argument is either not present or pass the given contract, can not be used outside of `KeywordArgs` contract, e.g. `Optional[Num]` + * [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/KeywordArgs) – checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts, e.g. `KeywordArgs[:number => Num, :description => Optional[String]]` + * [`Optional`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Optional) – checks that the keyword argument is either not present or pass the given contract, can not be used outside of `KeywordArgs` contract, e.g. `Optional[Num]` * Duck typing - * [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]` - * [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Send) – checks that all named methods return a truthy value, e.g. `Send[:valid?]` + * [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]` + * [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Send) – checks that all named methods return a truthy value, e.g. `Send[:valid?]` * Miscellaneous - * [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`. - * [`Eq`](http://www.rubydoc.info/gems/contracts/Contracts/Eq) – checks that the argument is precisely equal to the given value, e.g. `Eq[String]` matches the class `String` and not a string instance. - * [`Func`](http://www.rubydoc.info/gems/contracts/Contracts/Func) – specifies the contract for a proc/lambda e.g. `Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]`. See section "Contracts On Functions". + * [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`. + * [`Eq`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Eq) – checks that the argument is precisely equal to the given value, e.g. `Eq[String]` matches the class `String` and not a string instance. + * [`Func`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Func) – specifies the contract for a proc/lambda e.g. `Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]`. See section "Contracts On Functions". -To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts). +To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts/Builtin). It is recommended to use shortcut for referring builtin contracts: