@@ -109,7 +109,7 @@ def initialize(needed, set = nil)
109109 @all_specs = Hash . new { |h , name | h [ name ] = filter_specs ( @unfiltered_specs [ name ] ) }
110110 @all_versions = Hash . new { |h , pkg | h [ pkg ] = @all_specs [ pkg . to_s ] . map ( &:version ) . uniq . sort }
111111 @sorted_versions = Hash . new do |h , pkg |
112- h [ pkg ] = Gem ::PubGrub ::Package . root? ( pkg ) ? [ @root_version ] : filter_versions ( pkg )
112+ h [ pkg ] = Gem ::PubGrub ::Package . root? ( pkg ) ? [ @root_version ] : @all_versions [ pkg ]
113113 end
114114 @cached_dependencies = Hash . new do |h , pkg |
115115 h [ pkg ] = if Gem ::PubGrub ::Package . root? ( pkg )
@@ -133,14 +133,8 @@ def resolve
133133 all = @set . find_all ( dep_request )
134134 matching = select_local_platforms ( all )
135135
136- if matching . empty?
137- exc = Gem ::UnsatisfiableDependencyError . new ( dep_request , all )
138- exc . errors = @set . errors
139- raise exc
140- end
136+ next unless matching . empty?
141137
142- specs_matching_requirement = matching . select { |s | dep . requirement . satisfied_by? ( s . version ) }
143- next unless specs_matching_requirement . empty?
144138 exc = Gem ::UnsatisfiableDependencyError . new ( dep_request , all )
145139 exc . errors = @set . errors
146140 raise exc
@@ -149,7 +143,7 @@ def resolve
149143 solver = Gem ::PubGrub ::VersionSolver . new (
150144 source : self ,
151145 root : @root_package ,
152- strategy : Gem ::Resolver ::Strategy . new ( self , @root_package ) ,
146+ strategy : Gem ::Resolver ::Strategy . new ( self ) ,
153147 logger : make_logger
154148 )
155149 result = solver . solve
@@ -198,7 +192,21 @@ def all_versions_for(package)
198192 end
199193
200194 def versions_for ( package , range = Gem ::PubGrub ::VersionRange . any )
201- @versions_for_cache [ [ package , range ] ] ||= range . select_versions ( @sorted_versions [ package ] )
195+ @versions_for_cache [ [ package , range ] ] ||= begin
196+ candidates = range . select_versions ( @sorted_versions [ package ] )
197+
198+ if Gem ::PubGrub ::Package . root? ( package ) ||
199+ ( @set . respond_to? ( :prerelease ) && @set . prerelease ) ||
200+ range_admits_prerelease? ( range )
201+ candidates
202+ elsif @all_versions [ package ] . any? { |v | !v . prerelease? }
203+ candidates . reject ( &:prerelease? )
204+ else
205+ # Only prereleases exist for this gem; fall back to them so
206+ # dependencies like `>= 1.0` can still be satisfied.
207+ candidates
208+ end
209+ end
202210 end
203211
204212 def no_versions_incompatibility_for ( _package , unsatisfied_term )
@@ -307,9 +315,6 @@ def package_for(name)
307315 @packages [ name ] ||= Gem ::PubGrub ::Package . new ( name )
308316 end
309317
310- # Filter versions to exclude prereleases unless prerelease is enabled.
311- # Both all_versions_for and versions_for use this filtered set to ensure
312- # PubGrub's constraint propagation and version selection are consistent.
313318 def root_dependencies
314319 deps = { }
315320 @needed . each do |dep |
@@ -319,24 +324,16 @@ def root_dependencies
319324 deps
320325 end
321326
322- def filter_versions ( package )
323- all_versions = @all_versions [ package ]
324- if ( @set . respond_to? ( :prerelease ) && @set . prerelease ) || root_requires_prerelease? ( package )
325- all_versions
326- else
327- stable = all_versions . reject ( & :prerelease? )
328- stable . empty? ? all_versions : stable
327+ # Only the min bound is inspected: `~>` synthesises a max like `X.A`
328+ # whose suffix looks prerelease to Gem::Version but is not the user's
329+ # intent, so checking max would mis-admit prereleases for every `~>`.
330+ def range_admits_prerelease? ( range )
331+ range . ranges . any? do | r |
332+ next false if r . empty?
333+ r . min &. prerelease?
329334 end
330335 end
331336
332- # Root deps with an explicit prerelease requirement (e.g. `= 4.1.0.dev`)
333- # must keep their prerelease versions in the filtered set; otherwise
334- # PubGrub cannot match them even though `@set.find_all` returned them.
335- def root_requires_prerelease? ( package )
336- name = package . to_s
337- @needed . any? { |dep | dep . name == name && dep . requirement . prerelease? }
338- end
339-
340337 def find_unfiltered_specs_for ( name )
341338 dep = Gem ::Dependency . new ( name , ">= 0.a" )
342339 dep_request = DependencyRequest . new ( dep , nil )
@@ -413,9 +410,17 @@ def build_extended_explanation(name, constraint)
413410
414411 filtered = @all_specs [ name ]
415412 pkg = package_for ( name )
416- has_prerelease_filtering = @all_versions [ pkg ] . length > @sorted_versions [ pkg ] . length
417413
418- return if filtered . length == unfiltered . length && !has_prerelease_filtering
414+ # A prerelease hint applies when the source would strip prereleases for
415+ # this constraint (global prerelease flag off and the constraint's range
416+ # doesn't itself reach into prerelease territory) AND a prerelease of
417+ # the gem exists somewhere.
418+ prerelease_gated = !( @set . respond_to? ( :prerelease ) && @set . prerelease ) &&
419+ !range_admits_prerelease? ( constraint . range )
420+ has_prerelease_candidate = prerelease_gated &&
421+ @all_versions [ pkg ] . any? ( &:prerelease? )
422+
423+ return if filtered . length == unfiltered . length && !has_prerelease_candidate
419424
420425 hints = [ ]
421426
@@ -450,9 +455,8 @@ def build_extended_explanation(name, constraint)
450455 end
451456
452457 # Check for specs filtered by prerelease status
453- unless @set . respond_to? ( :prerelease ) && @set . prerelease
454- pkg = package_for ( name )
455- prerelease_versions = @all_versions [ pkg ] . select ( &:prerelease? ) - @sorted_versions [ pkg ]
458+ if prerelease_gated
459+ prerelease_versions = @all_versions [ pkg ] . select ( &:prerelease? )
456460 if prerelease_versions . any?
457461 versions = prerelease_versions . sort . reverse . first ( 3 ) # limit to avoid cluttering error output
458462 hints << "#{ name } #{ versions . join ( ", " ) } are pre-release versions. Use --prerelease to allow pre-release gems."
0 commit comments