Repository: pypa/pip

Issue:
pip resolver searches for target itself and fails
I did not manage to create a small reproducible of this in isolation, however, something is definitely off. Creating a tox test suite fails for virtualenv project:

```bash
git clone https://github.com/pypa/virtualenv
env PIP_UNSTABLE_FEATURE="resolver" VIRTUALENV_PIP=20.2b1 tox -re py38 --notest
```

throws:

```bash
env PIP_UNSTABLE_FEATURE="resolver" VIRTUALENV_PIP=20.2b1 tox -re py38 --notest                                                                                                                      .package recreate: /Users/bernat/git/github/virtualenv/.tox/.package
.package installdeps: setuptools >= 41.0.0, wheel >= 0.30.0, setuptools_scm >= 2
py38 recreate: /Users/bernat/git/github/virtualenv/.tox/py38
py38 installdeps: pip >= 20.0.2
py38 inst: /Users/bernat/git/github/virtualenv/.tox/.tmp/package/1/virtualenv-20.0.21.dev10+gc364311.tar.gz
ERROR: invocation failed (exit code 1), logfile: /Users/bernat/git/github/virtualenv/.tox/py38/log/py38-2.log
================================================================================================================================== log start ===================================================================================================================================
Looking in indexes: http://localhost:3141/bernat/bb
Processing ./.tox/.tmp/package/1/virtualenv-20.0.21.dev10+gc364311.tar.gz
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
    Preparing wheel metadata: started
    Preparing wheel metadata: finished with status 'done'
ERROR: Could not find a version that satisfies the requirement virtualenv==20.0.21.dev10+gc364311 (from virtualenv[testing])
ERROR: No matching distribution found for virtualenv
```

When switching to verbose mode I can see:

```
Given no hashes to check 0 links for project 'virtualenv': discarding no candidates
ERROR: Could not find a version that satisfies the requirement virtualenv==20.0.21.dev10+gc364311 (from virtualenv[testing])
ERROR: No matching distribution found for virtualenv
Exception information:
Traceback (most recent call last):
  File "/Users/bernat/git/github/virtualenv/.tox/py38/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/resolver.py", line 136, in resolve
    self._result = resolver.resolve(
  File "/Users/bernat/git/github/virtualenv/.tox/py38/lib/python3.8/site-packages/pip/_vendor/resolvelib/resolvers.py", line 413, in resolve
    state = resolution.resolve(requirements, max_rounds=max_rounds)
  File "/Users/bernat/git/github/virtualenv/.tox/py38/lib/python3.8/site-packages/pip/_vendor/resolvelib/resolvers.py", line 319, in resolve
    raise ResolutionImpossible(causes)
pip._vendor.resolvelib.resolvers.ResolutionImpossible: [RequirementInformation(requirement=SpecifierRequirement('virtualenv==20.0.21.dev10+gc364311'), parent=ExtrasCandidate(base=LinkCandidate('file:///Users/bernat/git/github/virtualenv/.tox/.tmp/package/1/virtualenv-20.0.21.dev10%2Bgc364311.tar.gz'), extras={'testing'}))]
```


Discussion:
Sounds similar to this section of failures:

https://hackmd.io/TkGB1hPQTcu9y_4JwAtGyA?view#Direct-URL-with-extras

Not sure if we already have a tracking issue for this, so I’m labelling this as a normal bug report.
This is definitely a case of non-specifier requirement that has extras (similar to `.[dev]`). I'd argue this isn't a bug as much as it is a TODO for the new resolver. :P
We even have a FIXME comment in the code for it. (Which I'm looking at now. When was the last time I said I hate extras? 🙂)
I just call all found errors a feature gap compared to the current no dependency resolver 😁 bug or feature, that's detail under the hood. 😎
> (Which I'm looking at now. When was the last time I said I hate extras? 🙂)

More than a day ago, so you can say that again. :)
> When was the last time I said I hate extras?

More than two weeks ago, so definitely allowed again in my case.
FWIW, I think have a bugfix branch for this locally, so I'll be filing that sometime next week. (once my college's last report submission is done with)

Resolving patch:
diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py
--- a/src/pip/_internal/resolution/resolvelib/candidates.py
+++ b/src/pip/_internal/resolution/resolvelib/candidates.py
@@ -347,8 +347,8 @@ class ExtrasCandidate(Candidate):
        to treat it as a separate node in the dependency graph.
     2. When we're getting the candidate's dependencies,
        a) We specify that we want the extra dependencies as well.
-       b) We add a dependency on the base candidate (matching the name and
-          version).  See below for why this is needed.
+       b) We add a dependency on the base candidate.
+          See below for why this is needed.
     3. We return None for the underlying InstallRequirement, as the base
        candidate will provide it, and we don't want to end up with duplicates.
 
@@ -417,6 +417,10 @@ def iter_dependencies(self):
                 extra
             )
 
+        # Add a dependency on the exact base
+        # (See note 2b in the class docstring)
+        yield factory.make_requirement_from_candidate(self.base)
+
         for r in self.base.dist.requires(valid_extras):
             requirement = factory.make_requirement_from_spec_matching_extras(
                 str(r), self.base._ireq, valid_extras,
@@ -424,13 +428,6 @@ def iter_dependencies(self):
             if requirement:
                 yield requirement
 
-        # Add a dependency on the exact base.
-        # (See note 2b in the class docstring)
-        # FIXME: This does not work if the base candidate is specified by
-        # link, e.g. "pip install .[dev]" will fail.
-        spec = "{}=={}".format(self.base.name, self.base.version)
-        yield factory.make_requirement_from_spec(spec, self.base._ireq)
-
     def get_install_requirement(self):
         # type: () -> Optional[InstallRequirement]
         # We don't return anything here, because we always
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -180,12 +180,16 @@ def make_requirement_from_install_req(self, ireq):
             # TODO: Get name and version from ireq, if possible?
             #       Specifically, this might be needed in "name @ URL"
             #       syntax - need to check where that syntax is handled.
-            cand = self._make_candidate_from_link(
+            candidate = self._make_candidate_from_link(
                 ireq.link, extras=set(ireq.extras), parent=ireq,
             )
-            return ExplicitRequirement(cand)
+            return self.make_requirement_from_candidate(candidate)
         return SpecifierRequirement(ireq, factory=self)
 
+    def make_requirement_from_candidate(self, candidate):
+        # type: (Candidate) -> ExplicitRequirement
+        return ExplicitRequirement(candidate)
+
     def make_requirement_from_spec(self, specifier, comes_from):
         # type: (str, InstallRequirement) -> Requirement
         ireq = self._make_install_req_from_spec(specifier, comes_from)


Test patch:
diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py
--- a/tests/functional/test_new_resolver.py
+++ b/tests/functional/test_new_resolver.py
@@ -825,9 +825,7 @@ def _wheel_from_index(script, name, version, requires, extras):
     @pytest.mark.parametrize(
         "pkg_builder",
         [
-            pytest.param(
-                _local_with_setup, marks=pytest.mark.xfail(strict=True),
-            ),
+            _local_with_setup,
             _direct_wheel,
             _wheel_from_index,
         ],