Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
@apply rule cannot pick up grouped utility css selector 
<!-- Please provide all of the information requested below. We're a small team and without all of this information it's not possible for us to help and your bug report will be closed. -->

**What version of Tailwind CSS are you using?**

**2.2.19**

**What build tool (or framework if it abstracts the build tool) are you using?**

**postcss-cli 8.3.1**

**What version of Node.js are you using?**

**v16.13.2**

**What browser are you using?**

**Chrome**

**What operating system are you using?**

**MacOS**

**Reproduction URL**

https://gist.github.com/ALexander4295502/6fef8dfb343bc94a970700715460d172

**Describe your issue**

I am using a grouped css selector in my utility `colors.css` file as shown in the gist above (grouped selectors are `html.dark .dark\:v-dark-bg-backdrop-modal` and `.v-dark-bg-backdrop-modal`)
```css
/* colors.css */
@layer utilities {
	@variants focus, hover, active, visited {
		/* purgecss start ignore */

		html.dark .dark\:v-dark-bg-backdrop-modal,
		.v-dark-bg-backdrop-modal {
			background-color: rgba(70, 71, 101, 0.8);
		}
		/* purgecss end ignore */
	}
}
```
And in one of my component css file, I am applying **one** of the grouped selectors to my component
```css
@import './colors.css';

html.dark my-modal {
	@apply v-dark-bg-backdrop-modal;
}
```
And after running postcss cli command the `html.dark my-modal` is disappeared in the output CSS file, but if I separate the grouped selectos as separate selectors as below:
```css
html.dark .dark\:v-dark-bg-backdrop-modal {
			background-color: rgba(70, 71, 101, 0.8);
		}

		.v-dark-bg-backdrop-modal {
			background-color: rgba(70, 71, 101, 0.8);
		}
```

There won't be any issues. As I didn't find any note in https://tailwindcss.com/docs/functions-and-directives#apply saying that `@apply` cannot be used for grouped selector, hence I think this is a bug needs to be fixed.


Hints:
I just upgraded our version of tailwind to V3 and now compilation fails because of group classes with `@apply`

<img width="797" alt="Screenshot 2022-04-13 at 19 57 46" src="https://user-images.githubusercontent.com/12516195/163252189-6d940bc8-2ffe-4fec-981c-59166b2ae12e.png">

Fulfilled to consider your to be as I would survey I have an unclear issue, I am likewise confused and requiring light on this not well characterized issue. Need help.
[epayitonline](https://www.epayitonline.vip/)
Hi wondering if someone can take a look at this issue?
> I just upgraded our version of tailwind to V3 and now compilation fails because of group classes with `@apply`
> 
> <img alt="Screenshot 2022-04-13 at 19 57 46" width="797" src="https://user-images.githubusercontent.com/12516195/163252189-6d940bc8-2ffe-4fec-981c-59166b2ae12e.png">

Does that mean the grouped class selector is not supported to be used in the tailwind css? I didn't find a documentation mentioning that.

Patch:
diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js
--- a/src/lib/expandApplyAtRules.js
+++ b/src/lib/expandApplyAtRules.js
@@ -8,18 +8,30 @@ import escapeClassName from '../util/escapeClassName'
 /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
 
 function extractClasses(node) {
-  let classes = new Set()
+  /** @type {Map<string, Set<string>>} */
+  let groups = new Map()
+
   let container = postcss.root({ nodes: [node.clone()] })
 
   container.walkRules((rule) => {
     parser((selectors) => {
       selectors.walkClasses((classSelector) => {
+        let parentSelector = classSelector.parent.toString()
+
+        let classes = groups.get(parentSelector)
+        if (!classes) {
+          groups.set(parentSelector, (classes = new Set()))
+        }
+
         classes.add(classSelector.value)
       })
     }).processSync(rule.selector)
   })
 
-  return Array.from(classes)
+  let normalizedGroups = Array.from(groups.values(), (classes) => Array.from(classes))
+  let classes = normalizedGroups.flat()
+
+  return Object.assign(classes, { groups: normalizedGroups })
 }
 
 function extractBaseCandidates(candidates, separator) {
@@ -353,10 +365,23 @@ function processApply(root, context, localCache) {
     let siblings = []
 
     for (let [applyCandidate, important, rules] of candidates) {
+      let potentialApplyCandidates = [
+        applyCandidate,
+        ...extractBaseCandidates([applyCandidate], context.tailwindConfig.separator),
+      ]
+
       for (let [meta, node] of rules) {
         let parentClasses = extractClasses(parent)
         let nodeClasses = extractClasses(node)
 
+        // When we encounter a rule like `.dark .a, .b { … }` we only want to be left with `[.dark, .a]` if the base applyCandidate is `.a` or with `[.b]` if the base applyCandidate is `.b`
+        // So we've split them into groups
+        nodeClasses = nodeClasses.groups
+          .filter((classList) =>
+            classList.some((className) => potentialApplyCandidates.includes(className))
+          )
+          .flat()
+
         // Add base utility classes from the @apply node to the list of
         // classes to check whether it intersects and therefore results in a
         // circular dependency or not.


Test patch:
diff --git a/tests/apply.test.js b/tests/apply.test.js
--- a/tests/apply.test.js
+++ b/tests/apply.test.js
@@ -658,6 +658,94 @@ it('should throw when trying to apply an indirect circular dependency with a mod
   })
 })
 
+it('should not throw when the circular dependency is part of a different selector (1)', () => {
+  let config = {
+    content: [{ raw: html`<div class="c"></div>` }],
+    plugins: [],
+  }
+
+  let input = css`
+    @tailwind utilities;
+
+    @layer utilities {
+      html.dark .a,
+      .b {
+        color: red;
+      }
+    }
+
+    html.dark .c {
+      @apply b;
+    }
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      html.dark .c {
+        color: red;
+      }
+    `)
+  })
+})
+
+it('should not throw when the circular dependency is part of a different selector (2)', () => {
+  let config = {
+    content: [{ raw: html`<div class="c"></div>` }],
+    plugins: [],
+  }
+
+  let input = css`
+    @tailwind utilities;
+
+    @layer utilities {
+      html.dark .a,
+      .b {
+        color: red;
+      }
+    }
+
+    html.dark .c {
+      @apply hover:b;
+    }
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      html.dark .c:hover {
+        color: red;
+      }
+    `)
+  })
+})
+
+it('should throw when the circular dependency is part of the same selector', () => {
+  let config = {
+    content: [{ raw: html`<div class="c"></div>` }],
+    plugins: [],
+  }
+
+  let input = css`
+    @tailwind utilities;
+
+    @layer utilities {
+      html.dark .a,
+      html.dark .b {
+        color: red;
+      }
+    }
+
+    html.dark .c {
+      @apply hover:b;
+    }
+  `
+
+  return run(input, config).catch((err) => {
+    expect(err.reason).toBe(
+      'You cannot `@apply` the `hover:b` utility here because it creates a circular dependency.'
+    )
+  })
+})
+
 it('rules with vendor prefixes are still separate when optimizing defaults rules', () => {
   let config = {
     experimental: { optimizeUniversalDefaults: true },