Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
v2 to v3 theme() keys bug?
**What version of Tailwind CSS are you using?**

v3.0.1

**What build tool (or framework if it abstracts the build tool) are you using?**

vue cli v5.x webpack v5.x

**What version of Node.js are you using?**

v16.5.0

**What browser are you using?**

N/A

**What operating system are you using?**

macOS

**Describe your issue**

Develop or compile. This message always appears

```
 does not exist in your theme config. 'spacing' has the following keys: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '14', '16', '18', '20', '24', '28', '32', '36', '40', '44', '48', '52', '56', '60', '64', '72', '80', '96', 'px', '0.5', '1.5', '2.5', '3.5', '4.5', '7.5'

  17 | }
  18 | .dialog.iOS .title {
> 19 |   font-size: theme("spacing['4.5']");
     |   ^
  20 |   color: theme("colors.black");
  21 | }
```

But the following keys message contains the index

There is no such error message in v2

tailwind.config.js

```
module.exports = {
  theme: {
    spacing: {
      ["4.5"]: "1.125rem"
    },
  }
}
```


Hints:
I think it's wrong to use array in object definition, try with this.

```
module.exports = {
  theme: {
    spacing: {
      "4.5": "1.125rem"
    },
  }
}
```

By the way why are you using space in font size? and if you want to not override default spacing values you need to extend the theme this way

```
  theme: {
    extend: {
```
@jheng-jie was just here to create a ticket for the same problem. we experience this as well, and it is a blocker for migration.

@minimit it is not an [array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names) so it won't make a difference. 
Hey! Can you please provide a reproduction?
@adamwathan [here is one](https://github.com/ardaerzin/tailwind3-spacing)

in the `Test.module.css` file if you enable the commented out lines, you'll start seeing the errors. this was working fine in tailwind v2. also the regular classnames still work as you can see in the index page
@adamwathan 

Sorry my project so big

But it can be easily reappear

```shell
# vite
yarn create vite test && cd test

# tailwindcss
yarn add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

tailwind.config.js
```javascript
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      spacing: {
        ["4.5"]: "0.125rem",
        ["4d5"]: "0.125rem",
      }
    },
  },
  plugins: [],
}
```

src/App.vue
```vue
<template>
  <div class="test">Test</div>
  <!-- is working -->
  <div class="p-4.5">Test</div>
</template>

<style>
@tailwind base;
@tailwind components;
@tailwind utilities;

.test {
  /*
    working
  */
  padding: theme("spacing.4d5");

  /*
    has the following keys:
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '14', '16', '20', '24',
      '28', '32', '36', '40', '44', '48', '52', '56', '60', '64', '72', '80', '96', 'px', '0.5',
      '1.5', '2.5', '3.5', '4.5', '4d5'
  */
  padding: theme("spacing['4.5']");

  /*
    'spacing[4.5]' does not exist in your theme config. 'spacing.4' is not an object.
  */
  padding: theme("spacing[4.5]");
}
</style>
```

The problem may occur in the "dot" naming
I don't think it's related to the dot naming, looks like the `[]` don't work anymore.

Smallest repro possible: https://play.tailwindcss.com/SvPNpftcow?file=config
Switching to `v2.2.19` makes it work, switching to `v3.0.1` doesn't.
I can confirm this. It happend with height[1.5] or a custom transitionProperty

Patch:
diff --git a/src/lib/evaluateTailwindFunctions.js b/src/lib/evaluateTailwindFunctions.js
--- a/src/lib/evaluateTailwindFunctions.js
+++ b/src/lib/evaluateTailwindFunctions.js
@@ -42,7 +42,7 @@ function validatePath(config, path, defaultValue) {
     ? pathToString(path)
     : path.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
   const pathSegments = Array.isArray(path) ? path : toPath(pathString)
-  const value = dlv(config.theme, pathString, defaultValue)
+  const value = dlv(config.theme, pathSegments, defaultValue)
 
   if (value === undefined) {
     let error = `'${pathString}' does not exist in your theme config.`
diff --git a/src/util/toPath.js b/src/util/toPath.js
--- a/src/util/toPath.js
+++ b/src/util/toPath.js
@@ -1,4 +1,26 @@
+/**
+ * Parse a path string into an array of path segments.
+ *
+ * Square bracket notation `a[b]` may be used to "escape" dots that would otherwise be interpreted as path separators.
+ *
+ * Example:
+ * a -> ['a]
+ * a.b.c -> ['a', 'b', 'c']
+ * a[b].c -> ['a', 'b', 'c']
+ * a[b.c].e.f -> ['a', 'b.c', 'e', 'f']
+ * a[b][c][d] -> ['a', 'b', 'c', 'd']
+ *
+ * @param {string|string[]} path
+ **/
 export function toPath(path) {
   if (Array.isArray(path)) return path
-  return path.split(/[\.\]\[]+/g)
+
+  let openBrackets = path.split('[').length - 1
+  let closedBrackets = path.split(']').length - 1
+
+  if (openBrackets !== closedBrackets) {
+    throw new Error(`Path is invalid. Has unbalanced brackets: ${path}`)
+  }
+
+  return path.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean)
 }


Test patch:
diff --git a/tests/evaluateTailwindFunctions.test.js b/tests/evaluateTailwindFunctions.test.js
--- a/tests/evaluateTailwindFunctions.test.js
+++ b/tests/evaluateTailwindFunctions.test.js
@@ -31,6 +31,111 @@ test('it looks up values in the theme using dot notation', () => {
   })
 })
 
+test('it looks up values in the theme using bracket notation', () => {
+  let input = css`
+    .banana {
+      color: theme('colors[yellow]');
+    }
+  `
+
+  let output = css`
+    .banana {
+      color: #f7cc50;
+    }
+  `
+
+  return run(input, {
+    theme: {
+      colors: {
+        yellow: '#f7cc50',
+      },
+    },
+  }).then((result) => {
+    expect(result.css).toEqual(output)
+    expect(result.warnings().length).toBe(0)
+  })
+})
+
+test('it looks up values in the theme using consecutive bracket notation', () => {
+  let input = css`
+    .banana {
+      color: theme('colors[yellow][100]');
+    }
+  `
+
+  let output = css`
+    .banana {
+      color: #f7cc50;
+    }
+  `
+
+  return run(input, {
+    theme: {
+      colors: {
+        yellow: {
+          100: '#f7cc50',
+        },
+      },
+    },
+  }).then((result) => {
+    expect(result.css).toEqual(output)
+    expect(result.warnings().length).toBe(0)
+  })
+})
+
+test('it looks up values in the theme using bracket notation that have dots in them', () => {
+  let input = css`
+    .banana {
+      padding-top: theme('spacing[1.5]');
+    }
+  `
+
+  let output = css`
+    .banana {
+      padding-top: 0.375rem;
+    }
+  `
+
+  return run(input, {
+    theme: {
+      spacing: {
+        '1.5': '0.375rem',
+      },
+    },
+  }).then((result) => {
+    expect(result.css).toEqual(output)
+    expect(result.warnings().length).toBe(0)
+  })
+})
+
+test('theme with mismatched brackets throws an error ', async () => {
+  let config = {
+    theme: {
+      spacing: {
+        '1.5': '0.375rem',
+      },
+    },
+  }
+
+  let input = (path) => css`
+    .banana {
+      padding-top: theme('${path}');
+    }
+  `
+
+  await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
+    `Path is invalid. Has unbalanced brackets: spacing[1.5]]`
+  )
+
+  await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
+    `Path is invalid. Has unbalanced brackets: spacing[[1.5]`
+  )
+
+  await expect(run(input('spacing[a['), config)).rejects.toThrowError(
+    `Path is invalid. Has unbalanced brackets: spacing[a[`
+  )
+})
+
 test('color can be a function', () => {
   let input = css`
     .backgroundColor {
diff --git a/tests/to-path.test.js b/tests/to-path.test.js
--- a/tests/to-path.test.js
+++ b/tests/to-path.test.js
@@ -7,11 +7,12 @@ it('should keep an array as an array', () => {
 })
 
 it.each`
-  input         | output
-  ${'a.b.c'}    | ${['a', 'b', 'c']}
-  ${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
-  ${'.a'}       | ${['', 'a']}
-  ${'[].a'}     | ${['', 'a']}
+  input             | output
+  ${'a.b.c'}        | ${['a', 'b', 'c']}
+  ${'a[0].b.c'}     | ${['a', '0', 'b', 'c']}
+  ${'.a'}           | ${['a']}
+  ${'[].a'}         | ${['a']}
+  ${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']}
 `('should convert "$input" to "$output"', ({ input, output }) => {
   expect(toPath(input)).toEqual(output)
 })