Repository: pypa/pip

Issue:
'pip wheel --no-deps' doesn't work with extras on the new resolver

**What did you want to do?**

`pip wheel --use-feature=2020-resolver --no-deps splitio_client[cpphash,redis]==8.2.0 -w wheels/` should download `splitio_client-8.2.0-py2.py3-none-any.whl` like `pip wheel --no-deps splitio_client[cpphash,redis]==8.2.0 -w wheels/` does.

On its own this is an artificial case, but I broke it out of a 150 line requirements file that was failing to find the issue. :)

**Output**

#### With --use-feature=2020-resolver

```console
$ pip wheel --use-feature=2020-resolver --no-deps splitio_client[cpphash,redis]==8.2.0 -w wheels/
```

(no output)

#### Without --use-feature=2020-resolver

```console
$ pip wheel --no-deps splitio_client[cpphash,redis]==8.2.0 -w wheels/
Processing /Users/andy/Library/Caches/pip/wheels/5a/9a/1e/de8c54b6448f1a2615e76d5d2e7395342f9d1370865d2c0566/splitio_client-8.2.0-py2.py3-none-any.whl
  Saved ./wheels/splitio_client-8.2.0-py2.py3-none-any.whl
Skipping splitio-client, due to already being wheel.
```


Discussion:
Can reproduce with master.
Ah, I think I know the problem. An extra-ed package is implemented as a virtual package with a dependency on its non-extra-ed self, but `--no-deps` makes the new resolver skip all dependencies, including that.

Note that `wheel` has nothing to do with the problem. It can be reproduced with `install` as well. The base package won’t be installed if extras are specified in conjunction with `--no-deps`.

Resolving patch:
diff --git a/src/pip/_internal/resolution/resolvelib/base.py b/src/pip/_internal/resolution/resolvelib/base.py
--- a/src/pip/_internal/resolution/resolvelib/base.py
+++ b/src/pip/_internal/resolution/resolvelib/base.py
@@ -69,8 +69,8 @@ def source_link(self):
         # type: () -> Optional[Link]
         raise NotImplementedError("Override in subclass")
 
-    def iter_dependencies(self):
-        # type: () -> Iterable[Optional[Requirement]]
+    def iter_dependencies(self, with_requires):
+        # type: (bool) -> Iterable[Optional[Requirement]]
         raise NotImplementedError("Override in subclass")
 
     def get_install_requirement(self):
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
@@ -275,8 +275,10 @@ def _get_requires_python_specifier(self):
             return None
         return spec
 
-    def iter_dependencies(self):
-        # type: () -> Iterable[Optional[Requirement]]
+    def iter_dependencies(self, with_requires):
+        # type: (bool) -> Iterable[Optional[Requirement]]
+        if not with_requires:
+            return
         for r in self.dist.requires():
             yield self._factory.make_requirement_from_spec(str(r), self._ireq)
         python_dep = self._factory.make_requires_python_requirement(
@@ -420,8 +422,10 @@ def format_for_error(self):
         # type: () -> str
         return "{} {} (Installed)".format(self.name, self.version)
 
-    def iter_dependencies(self):
-        # type: () -> Iterable[Optional[Requirement]]
+    def iter_dependencies(self, with_requires):
+        # type: (bool) -> Iterable[Optional[Requirement]]
+        if not with_requires:
+            return
         for r in self.dist.requires():
             yield self._factory.make_requirement_from_spec(str(r), self._ireq)
 
@@ -519,10 +523,16 @@ def source_link(self):
         # type: () -> Optional[Link]
         return self.base.source_link
 
-    def iter_dependencies(self):
-        # type: () -> Iterable[Optional[Requirement]]
+    def iter_dependencies(self, with_requires):
+        # type: (bool) -> Iterable[Optional[Requirement]]
         factory = self.base._factory
 
+        # Add a dependency on the exact base
+        # (See note 2b in the class docstring)
+        yield factory.make_requirement_from_candidate(self.base)
+        if not with_requires:
+            return
+
         # The user may have specified extras that the candidate doesn't
         # support. We ignore any unsupported extras here.
         valid_extras = self.extras.intersection(self.base.dist.extras)
@@ -535,10 +545,6 @@ 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(
                 str(r), self.base._ireq, valid_extras,
@@ -585,8 +591,8 @@ def format_for_error(self):
         # type: () -> str
         return "Python {}".format(self.version)
 
-    def iter_dependencies(self):
-        # type: () -> Iterable[Optional[Requirement]]
+    def iter_dependencies(self, with_requires):
+        # type: (bool) -> Iterable[Optional[Requirement]]
         return ()
 
     def get_install_requirement(self):
diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py
--- a/src/pip/_internal/resolution/resolvelib/provider.py
+++ b/src/pip/_internal/resolution/resolvelib/provider.py
@@ -145,6 +145,9 @@ def is_satisfied_by(self, requirement, candidate):
 
     def get_dependencies(self, candidate):
         # type: (Candidate) -> Sequence[Requirement]
-        if self._ignore_dependencies:
-            return []
-        return [r for r in candidate.iter_dependencies() if r is not None]
+        with_requires = not self._ignore_dependencies
+        return [
+            r
+            for r in candidate.iter_dependencies(with_requires)
+            if r is not None
+        ]


Test patch:
diff --git a/tests/yaml/extras.yml b/tests/yaml/extras.yml
--- a/tests/yaml/extras.yml
+++ b/tests/yaml/extras.yml
@@ -40,3 +40,10 @@ cases:
       - E 1.0.0
       - F 1.0.0
   skip: old
+-
+  request:
+    - install: D[extra_1]
+      options: --no-deps
+  response:
+    - state:
+      - D 1.0.0