Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit acd581e

Browse files
committed
Add satisfiability check for case variants
1 parent 892b5ca commit acd581e

File tree

4 files changed

+108
-3
lines changed

4 files changed

+108
-3
lines changed

internal/gps/satisfy.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ func (s *solver) check(a atomWithPackages, pkgonly bool) error {
5454
if err = s.checkIdentMatches(a, dep); err != nil {
5555
return err
5656
}
57+
if err = s.checkCaseConflicts(a, dep); err != nil {
58+
return err
59+
}
5760
if err = s.checkDepsConstraintsAllowable(a, dep); err != nil {
5861
return err
5962
}
@@ -218,6 +221,31 @@ func (s *solver) checkIdentMatches(a atomWithPackages, cdep completeDep) error {
218221
return nil
219222
}
220223

224+
// checkCaseConflicts ensures that the ProjectRoot specified in the completeDep
225+
// does not have case conflicts with any existing dependencies.
226+
//
227+
// We only need to check the ProjectRoot, rather than any packages therein, as
228+
// the later check for package existence is case-sensitive.
229+
func (s *solver) checkCaseConflicts(a atomWithPackages, cdep completeDep) error {
230+
pr := cdep.workingConstraint.Ident.ProjectRoot
231+
hasConflict, current := s.sel.findCaseConflicts(pr)
232+
if !hasConflict {
233+
return nil
234+
}
235+
236+
curid, _ := s.sel.getIdentFor(pr)
237+
deps := s.sel.getDependenciesOn(curid)
238+
for _, d := range deps {
239+
s.fail(d.depender.id)
240+
}
241+
242+
return &caseMismatchFailure{
243+
goal: dependency{depender: a.a, dep: cdep},
244+
current: current,
245+
failsib: deps,
246+
}
247+
}
248+
221249
// checkPackageImportsFromDepExist ensures that, if the dep is already selected,
222250
// the newly-required set of packages being placed on it exist and are valid.
223251
func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep completeDep) error {

internal/gps/selection.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
package gps
66

7+
import "strings"
8+
79
type selection struct {
810
projects []selected
911
deps map[ProjectRoot][]dependency
12+
prLenMap map[int][]ProjectRoot
1013
vu *versionUnifier
1114
}
1215

@@ -59,13 +62,43 @@ func (s *selection) popSelection() (atomWithPackages, bool) {
5962
return sel.a, sel.first
6063
}
6164

65+
func (s *selection) findCaseConflicts(pr ProjectRoot) (bool, ProjectRoot) {
66+
prlist, has := s.prLenMap[len(pr)]
67+
if !has {
68+
return false, ""
69+
}
70+
71+
// TODO(sdboyer) bug here if it's possible that strings.ToLower() could
72+
// change the length of the string
73+
lowpr := strings.ToLower(string(pr))
74+
for _, existing := range prlist {
75+
if lowpr != strings.ToLower(string(existing)) {
76+
continue
77+
}
78+
// If the converted strings match, then whatever we figure out here will
79+
// be definitive - we needn't walk the rest of the slice.
80+
if pr == existing {
81+
return false, ""
82+
} else {
83+
return true, existing
84+
}
85+
}
86+
87+
return false, ""
88+
}
89+
6290
func (s *selection) pushDep(dep dependency) {
63-
s.deps[dep.dep.Ident.ProjectRoot] = append(s.deps[dep.dep.Ident.ProjectRoot], dep)
91+
pr := dep.dep.Ident.ProjectRoot
92+
s.deps[pr] = append(s.deps[pr], dep)
93+
s.prLenMap[len(pr)] = append(s.prLenMap[len(pr)], pr)
6494
}
6595

6696
func (s *selection) popDep(id ProjectIdentifier) (dep dependency) {
6797
deps := s.deps[id.ProjectRoot]
6898
dep, s.deps[id.ProjectRoot] = deps[len(deps)-1], deps[:len(deps)-1]
99+
100+
prlist := s.prLenMap[len(id.ProjectRoot)]
101+
s.prLenMap[len(id.ProjectRoot)] = prlist[:len(prlist)-1]
69102
return dep
70103
}
71104

internal/gps/solve_failures.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,49 @@ func (e *noVersionError) traceString() string {
7171
return buf.String()
7272
}
7373

74+
// caseMismatcFailure occurs when there are import paths that differ only by
75+
// case. The compiler disallows this case.
76+
type caseMismatchFailure struct {
77+
// goal is the depender atom that tried to introduce the case-varying name,
78+
// along with the case-varying name.
79+
goal dependency
80+
// current is the specific casing of a ProjectRoot that is presently
81+
// selected for all possible case variations of its contained unicode code
82+
// points.
83+
current ProjectRoot
84+
// failsib is the list of active dependencies that have determined the
85+
// specific casing for the target project.
86+
failsib []dependency
87+
}
88+
89+
func (e *caseMismatchFailure) Error() string {
90+
if len(e.failsib) == 1 {
91+
str := "Could not introduce %s due to a case-only variation: it depends on %q, but %q was already established as the case variant for that project root by depender %s"
92+
return fmt.Sprintf(str, a2vs(e.goal.depender), e.goal.dep.Ident.ProjectRoot, e.current, a2vs(e.failsib[0].depender))
93+
}
94+
95+
var buf bytes.Buffer
96+
97+
str := "Could not introduce %s due to a case-only variation: it depends on %q, but %q was already established as the case variant for that project root by the following other dependers:\n"
98+
fmt.Fprintf(&buf, str, e.goal.dep.Ident.ProjectRoot, e.current, a2vs(e.goal.depender))
99+
100+
for _, c := range e.failsib {
101+
fmt.Fprintf(&buf, "\t%s\n", a2vs(c.depender))
102+
}
103+
104+
return buf.String()
105+
}
106+
107+
func (e *caseMismatchFailure) traceString() string {
108+
var buf bytes.Buffer
109+
fmt.Fprintf(&buf, "case-only variation in dependency on %q; %q already established by:\n", e.goal.dep.Ident.ProjectRoot, e.current)
110+
for _, f := range e.failsib {
111+
fmt.Fprintf(&buf, "%s\n", a2vs(f.depender))
112+
}
113+
114+
return buf.String()
115+
}
116+
74117
// disjointConstraintFailure occurs when attempting to introduce an atom that
75118
// itself has an acceptable version, but one of its dependency constraints is
76119
// disjoint with one or more dependency constraints already active for that

internal/gps/solver.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,9 @@ func Prepare(params SolveParameters, sm SourceManager) (Solver, error) {
307307

308308
// Initialize stacks and queues
309309
s.sel = &selection{
310-
deps: make(map[ProjectRoot][]dependency),
311-
vu: s.vUnify,
310+
deps: make(map[ProjectRoot][]dependency),
311+
prLenMap: make(map[int][]ProjectRoot),
312+
vu: s.vUnify,
312313
}
313314
s.unsel = &unselected{
314315
sl: make([]bimodalIdentifier, 0),

0 commit comments

Comments
 (0)