version: enable Sorbet type checking

This commit is contained in:
Seeker 2021-01-16 11:49:01 -08:00
parent 8c48547f0b
commit 32ebc02f2f
5 changed files with 135 additions and 39 deletions

View File

@ -22,7 +22,7 @@ module OS
sig { returns(Version) }
def minimum_version
Version.new ENV["HOMEBREW_LINUX_MINIMUM_GLIBC_VERSION"]
Version.new(ENV.fetch("HOMEBREW_LINUX_MINIMUM_GLIBC_VERSION"))
end
def below_minimum_version?

View File

@ -0,0 +1,10 @@
# typed: strict
module OS
module Mac
class Version
sig { returns(Token) }
def major; end
end
end
end

View File

@ -1,4 +1,4 @@
# typed: false
# typed: true
# frozen_string_literal: true
require "version/null"
@ -11,6 +11,7 @@ class Version
include Comparable
sig { params(name: T.any(String, Symbol), full: T::Boolean).returns(Regexp) }
def self.formula_optionally_versioned_regex(name, full: true)
/#{"^" if full}#{Regexp.escape(name)}(@\d[\d.]*)?#{"$" if full}/
end
@ -18,9 +19,12 @@ class Version
# A part of a {Version}.
class Token
extend T::Sig
extend T::Helpers
abstract!
include Comparable
sig { params(val: String).returns(Token) }
def self.create(val)
raise TypeError, "Token value must be a string; got a #{val.class} (#{val})" unless val.respond_to?(:to_str)
@ -36,39 +40,49 @@ class Version
end.new(val)
end
sig { params(val: T.untyped).returns(T.nilable(Token)) }
def self.from(val)
return NULL_TOKEN if val.nil? || (val.respond_to?(:null?) && val.null?)
case val
when Token then val
when String then Token.create(val)
when Integer then Token.create(val.to_s)
when nil then NULL_TOKEN
else NULL_TOKEN if val.respond_to?(:null?) && val.null?
end
end
sig { returns(T.nilable(T.any(String, Integer))) }
attr_reader :value
sig { params(value: T.nilable(T.any(String, Integer))).void }
def initialize(value)
@value = value
@value = T.let(value, T.untyped)
end
sig { abstract.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other); end
sig { returns(String) }
def inspect
"#<#{self.class.name} #{value.inspect}>"
end
sig { returns(Integer) }
def hash
value.hash
end
sig { returns(Float) }
def to_f
value.to_f
end
sig { returns(Integer) }
def to_i
value.to_i
end
sig { returns(String) }
def to_s
value.to_s
end
@ -78,16 +92,26 @@ class Version
def numeric?
false
end
sig { returns(T::Boolean) }
def null?
false
end
end
# A pseudo-token representing the absence of a token.
class NullToken < Token
extend T::Sig
sig { override.returns(NilClass) }
attr_reader :value
sig { void }
def initialize
super(nil)
end
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -103,12 +127,12 @@ class Version
end
end
sig { returns(T::Boolean) }
sig { override.returns(T::Boolean) }
def null?
true
end
sig { returns(String) }
sig { override.returns(String) }
def inspect
"#<#{self.class.name}>"
end
@ -122,12 +146,15 @@ class Version
class StringToken < Token
PATTERN = /[a-z]+/i.freeze
def initialize(value)
super
sig { override.returns(String) }
attr_reader :value
@value = value.to_s
sig { params(value: String).void }
def initialize(value)
super(value.to_s)
end
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -135,7 +162,7 @@ class Version
when StringToken
value <=> other.value
when NumericToken, NullToken
-Integer(other <=> self)
-T.must(other <=> self)
end
end
end
@ -145,12 +172,15 @@ class Version
PATTERN = /[0-9]+/i.freeze
extend T::Sig
def initialize(value)
super
sig { override.returns(Integer) }
attr_reader :value
@value = value.to_i
sig { params(value: T.any(String, Integer)).void }
def initialize(value)
super(value.to_i)
end
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -160,11 +190,11 @@ class Version
when StringToken
1
when NullToken
-Integer(other <=> self)
-T.must(other <=> self)
end
end
sig { returns(T::Boolean) }
sig { override.returns(T::Boolean) }
def numeric?
true
end
@ -172,6 +202,7 @@ class Version
# A token consisting of an alphabetic and a numeric part.
class CompositeToken < StringToken
sig { returns(Integer) }
def rev
value[/[0-9]+/].to_i
end
@ -181,6 +212,7 @@ class Version
class AlphaToken < CompositeToken
PATTERN = /alpha[0-9]*|a[0-9]+/i.freeze
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -199,6 +231,7 @@ class Version
class BetaToken < CompositeToken
PATTERN = /beta[0-9]*|b[0-9]+/i.freeze
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -219,6 +252,7 @@ class Version
class PreToken < CompositeToken
PATTERN = /pre[0-9]*/i.freeze
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -239,6 +273,7 @@ class Version
class RCToken < CompositeToken
PATTERN = /rc[0-9]*/i.freeze
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -259,6 +294,7 @@ class Version
class PatchToken < CompositeToken
PATTERN = /p[0-9]*/i.freeze
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -277,6 +313,7 @@ class Version
class PostToken < CompositeToken
PATTERN = /.post[0-9]+/i.freeze
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
return unless other = Token.from(other)
@ -303,10 +340,12 @@ class Version
).freeze
private_constant :SCAN_PATTERN
sig { params(url: T.any(String, Pathname), specs: T.untyped).returns(Version) }
def self.detect(url, **specs)
parse(specs.fetch(:tag, url), detected_from_url: true)
end
sig { params(val: String).returns(Version) }
def self.create(val)
raise TypeError, "Version value must be a string; got a #{val.class} (#{val})" unless val.respond_to?(:to_str)
@ -317,11 +356,13 @@ class Version
end
end
sig { params(spec: T.any(String, Pathname), detected_from_url: T::Boolean).returns(Version) }
def self.parse(spec, detected_from_url: false)
version = _parse(spec, detected_from_url: detected_from_url)
version.nil? ? NULL : new(version, detected_from_url: detected_from_url)
end
sig { params(spec: T.any(String, Pathname), detected_from_url: T::Boolean).returns(T.nilable(String)) }
def self._parse(spec, detected_from_url:)
spec = CGI.unescape(spec.to_s) if detected_from_url
@ -330,11 +371,11 @@ class Version
spec_s = spec.to_s
stem = if spec.directory?
spec.basename
spec.basename.to_s
elsif spec_s.match?(%r{((?:sourceforge\.net|sf\.net)/.*)/download$})
Pathname.new(spec.dirname).stem
elsif spec_s.match?(/\.[^a-zA-Z]+$/)
Pathname.new(spec_s).basename
spec.dirname.stem
elsif spec_s.match?(/\.[^a-zA-Z]+$/) # rubocop:disable Lint/DuplicateBranch
spec.basename.to_s
else
spec.stem
end
@ -358,7 +399,7 @@ class Version
# e.g. boost_1_39_0
m = /((?:\d+_)+\d+)$/.match(stem)
return m.captures.first.tr("_", ".") unless m.nil?
return T.must(m.captures.first).tr("_", ".") unless m.nil?
# e.g. foobar-4.5.1-1
# e.g. unrtf_0.20.4-1
@ -468,6 +509,7 @@ class Version
end
private_class_method :_parse
sig { params(val: T.any(String, Version), detected_from_url: T::Boolean).void }
def initialize(val, detected_from_url: false)
raise TypeError, "Version value must be a string; got a #{val.class} (#{val})" unless val.respond_to?(:to_str)
@ -475,18 +517,22 @@ class Version
@detected_from_url = detected_from_url
end
sig { returns(T::Boolean) }
def detected_from_url?
@detected_from_url
end
sig { returns(T::Boolean) }
def head?
false
end
sig { returns(T::Boolean) }
def null?
false
end
sig { params(other: T.untyped).returns(T.nilable(Integer)) }
def <=>(other)
# Needed to retain API compatibility with older string comparisons
# for compiler versions, etc.
@ -533,42 +579,52 @@ class Version
end
alias eql? ==
sig { returns(T.nilable(Token)) }
def major
tokens.first
end
sig { returns(T.nilable(Token)) }
def minor
tokens.second
end
sig { returns(T.nilable(Token)) }
def patch
tokens.third
end
sig { returns(Version) }
def major_minor
Version.new([major, minor].compact.join("."))
end
sig { returns(Version) }
def major_minor_patch
Version.new([major, minor, patch].compact.join("."))
end
sig { returns(T::Boolean) }
def empty?
version.empty?
end
sig { returns(Integer) }
def hash
version.hash
end
sig { returns(Float) }
def to_f
version.to_f
end
sig { returns(Integer) }
def to_i
version.to_i
end
sig { returns(String) }
def to_s
version.dup
end
@ -576,20 +632,24 @@ class Version
protected
sig { returns(String) }
attr_reader :version
sig { returns(T::Array[Token]) }
def tokens
@tokens ||= tokenize
end
private
sig { params(a: Integer, b: Integer).returns(Integer) }
def max(a, b)
(a > b) ? a : b
end
sig { returns(T::Array[Token]) }
def tokenize
version.scan(SCAN_PATTERN).map { |token| Token.create(token) }
version.scan(SCAN_PATTERN).map { |token| Token.create(T.cast(token, String)) }
end
end
@ -600,6 +660,7 @@ end
class HeadVersion < Version
extend T::Sig
sig { returns(T.nilable(String)) }
attr_reader :commit
def initialize(*)
@ -607,6 +668,7 @@ class HeadVersion < Version
@commit = @version[/^HEAD-(.+)$/, 1]
end
sig { params(commit: T.nilable(String)).void }
def update_commit(commit)
@commit = commit
@version = if commit

View File

@ -0,0 +1,10 @@
# typed: strict
class Version
# For `alias eql? ==`
# See discussions:
# - https://github.com/sorbet/sorbet/pull/1443
# - https://github.com/sorbet/sorbet/issues/2378
sig { params(other: T.untyped).returns(T::Boolean) }
def ==(other); end
end

View File

@ -1,17 +1,24 @@
# typed: true
# frozen_string_literal: true
require "singleton"
class Version
# Represents the absence of a version.
NULL = Class.new do
# A pseudo-version representing the absence of a version.
#
# @api private
class NullVersion < Version
extend T::Sig
include Comparable
include Singleton
sig { override.params(_other: T.untyped).returns(Integer) }
def <=>(_other)
-1
end
sig { override.params(_other: T.untyped).returns(T::Boolean) }
def eql?(_other)
# Makes sure that the same instance of Version::NULL
# will never equal itself; normally Comparable#==
@ -20,17 +27,17 @@ class Version
false
end
sig { returns(T::Boolean) }
sig { override.returns(T::Boolean) }
def detected_from_url?
false
end
sig { returns(T::Boolean) }
sig { override.returns(T::Boolean) }
def head?
false
end
sig { returns(T::Boolean) }
sig { override.returns(T::Boolean) }
def null?
true
end
@ -40,52 +47,59 @@ class Version
def requires_nehalem_cpu?
false
end
alias_method :requires_sse4?, :requires_nehalem_cpu?
alias_method :requires_sse41?, :requires_nehalem_cpu?
alias_method :requires_sse42?, :requires_nehalem_cpu?
alias_method :requires_popcnt?, :requires_nehalem_cpu?
alias requires_sse4? requires_nehalem_cpu?
alias requires_sse41? requires_nehalem_cpu?
alias requires_sse42? requires_nehalem_cpu?
alias requires_popcnt? requires_nehalem_cpu?
sig { override.returns(Token) }
def major
NULL_TOKEN
end
sig { override.returns(Token) }
def minor
NULL_TOKEN
end
sig { override.returns(Token) }
def patch
NULL_TOKEN
end
sig { returns(Version) }
sig { override.returns(Version) }
def major_minor
self
end
sig { returns(Version) }
sig { override.returns(Version) }
def major_minor_patch
self
end
sig { returns(Float) }
sig { override.returns(Float) }
def to_f
Float::NAN
end
sig { returns(Integer) }
sig { override.returns(Integer) }
def to_i
0
end
sig { returns(String) }
sig { override.returns(String) }
def to_s
""
end
alias_method :to_str, :to_s
alias to_str to_s
sig { returns(String) }
sig { override.returns(String) }
def inspect
"#<Version::NULL>"
end
end.new.freeze
end
private_constant :NullVersion
# Represents the absence of a version.
NULL = NullVersion.instance.freeze
end