Recognize URIs starting with `dart-macro+`.

Such URIs belong to the same package as the
the rest of the URI would, but are not `package:` URIs,
and do not have a corresponding `package:` URI.
In this way, they behave like files inside the package,
but outside of `lib/`, even if they are `dart-macro+package:`
URIs.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a682bc3..95a9c9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
-## 2.1.1-wip
+## 2.2.0-wip
 
 - Require Dart 3.2
+- Add support for `dart-macro+` scheme prefixes.
+  Recognizes these as belonging to the same package as their unprefixed URIs.
 
 ## 2.1.0
 
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index c00ac67..2225834 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -166,9 +166,13 @@
 
   /// Provides the associated package for a specific [file] (or directory).
   ///
-  /// Returns a [Package] which contains the [file]'s path, if any.
+  /// If the [file] is a `package:` URI, it must be a *valid* `package:` URI,
+  /// and the package with that URI's package name is returned, if one exists.
+  ///
+  /// Otherwise returns a [Package] which contains the [file]'s path.
   /// That is, the [Package.root] directory is a parent directory
-  /// of the [file]'s location.
+  /// of the [file]'s location, based on the root URI being a prefix of this
+  /// [file] URI.
   ///
   /// Returns `null` if the file does not belong to any package.
   Package? packageOf(Uri file);
@@ -190,7 +194,7 @@
   /// [Package.packageUriRoot] of the corresponding package.
   Uri? resolve(Uri packageUri);
 
-  /// The package URI which resolves to [nonPackageUri].
+  /// The package URI which resolves to [nonPackageUri], if any.
   ///
   /// The [nonPackageUri] must not have any query or fragment part,
   /// and it must not have `package` as scheme.
diff --git a/lib/src/package_config_impl.dart b/lib/src/package_config_impl.dart
index d8e8d49..b5205eb 100644
--- a/lib/src/package_config_impl.dart
+++ b/lib/src/package_config_impl.dart
@@ -142,7 +142,16 @@
   Package? operator [](String packageName) => _packages[packageName];
 
   @override
-  Package? packageOf(Uri file) => _packageTree.packageOf(file);
+  Package? packageOf(Uri uri) {
+    if (uri.isScheme('package') || uri.isScheme('dart-macro+package')) {
+      // Directly specifies its package.
+      var packageName = checkValidPackageUri(uri, 'uri', allowMacro: true);
+      return _packages[packageName];
+    }
+
+    /// Try finding a prefix in the package-config's `rootUri`s.
+    return _packageTree.packageOf(uri);
+  }
 
   @override
   Uri? resolve(Uri packageUri) {
@@ -161,14 +170,22 @@
       throw PackageConfigArgumentError(nonPackageUri, 'nonPackageUri',
           'Must not have query or fragment part');
     }
+    if (nonPackageUri.scheme.startsWith(dartMacroSchemePrefix)) {
+      // A macro-generated file has no `package:` URI.
+      return null;
+    }
     // Find package that file belongs to.
     var package = _packageTree.packageOf(nonPackageUri);
     if (package == null) return null;
-    // Check if it is inside the package URI root.
+    // Check if it is inside the package URI root (usually `lib/`).
     var path = nonPackageUri.toString();
-    var root = package.packageUriRoot.toString();
-    if (_beginsWith(package.root.toString().length, root, path)) {
-      var rest = path.substring(root.length);
+    var packageRoot = package.root.toString();
+    var packageUriRoot = package.packageUriRoot.toString();
+    assert(packageUriRoot.startsWith(packageRoot));
+    // This prefix was correctly matched by `packageOf`.
+    var matchedPrefix = packageRoot.length;
+    if (_beginsWith(matchedPrefix, packageUriRoot, matchedPrefix, path)) {
+      var rest = path.substring(packageUriRoot.length);
       return Uri(scheme: 'package', path: '${package.name}/$rest');
     }
     return null;
@@ -428,7 +445,7 @@
       }
       // 2) The existing package has a packageUriRoot thats inside the
       //    root of the new package.
-      if (_beginsWith(0, newPackage.root.toString(),
+      if (_beginsWith(0, newPackage.root.toString(), 0,
           existingPackage.packageUriRoot.toString())) {
         onError(ConflictException(
             newPackage, existingPackage, ConflictType.interleaving));
@@ -439,7 +456,7 @@
       // it thouh.
       // 3) The new package is inside the packageUriRoot of existing package.
       if (_disallowPackagesInsidePackageUriRoot) {
-        if (_beginsWith(0, existingPackage.packageUriRoot.toString(),
+        if (_beginsWith(0, existingPackage.packageUriRoot.toString(), 0,
             newPackage.root.toString())) {
           onError(ConflictException(
               newPackage, existingPackage, ConflictType.insidePackageRoot));
@@ -479,16 +496,18 @@
     _packages.add(newPackage);
   }
 
-  bool _isMatch(
-      String path, _PackageTrieNode node, List<SimplePackage> potential) {
+  /// Matches `path.substring(offset)` against the strings of `node`.
+  bool _isMatch(String path, int pathStart, _PackageTrieNode node,
+      List<SimplePackage> potential) {
     var currentPackage = node.package;
     if (currentPackage != null) {
       var currentPackageRootLength = currentPackage.root.toString().length;
-      if (path.length == currentPackageRootLength) return true;
+      if (path.length - pathStart == currentPackageRootLength) return true;
       var currentPackageUriRoot = currentPackage.packageUriRoot.toString();
       // Is [file] inside the package root of [currentPackage]?
       if (currentPackageUriRoot.length == currentPackageRootLength ||
-          _beginsWith(currentPackageRootLength, currentPackageUriRoot, path)) {
+          _beginsWith(currentPackageRootLength, currentPackageUriRoot,
+              currentPackageRootLength + pathStart, path)) {
         return true;
       }
       potential.add(currentPackage);
@@ -498,11 +517,18 @@
 
   @override
   SimplePackage? packageOf(Uri file) {
-    var currentTrieNode = _map[file.scheme];
+    var scheme = file.scheme;
+    var pathStart = 0;
+    if (scheme.startsWith(dartMacroSchemePrefix)) {
+      scheme = schemeFromMacroScheme(scheme);
+      pathStart = dartMacroSchemePrefix.length;
+    }
+    var currentTrieNode = _map[scheme];
     if (currentTrieNode == null) return null;
     var path = file.toString();
+    assert(pathStart == 0 || path.startsWith(dartMacroSchemePrefix));
     var potential = <SimplePackage>[];
-    if (_isMatch(path, currentTrieNode, potential)) {
+    if (_isMatch(path, pathStart, currentTrieNode, potential)) {
       return currentTrieNode.package;
     }
     var segments = file.pathSegments;
@@ -511,7 +537,7 @@
       var segment = segments[i];
       currentTrieNode = currentTrieNode!.map[segment];
       if (currentTrieNode == null) break;
-      if (_isMatch(path, currentTrieNode, potential)) {
+      if (_isMatch(path, pathStart, currentTrieNode, potential)) {
         return currentTrieNode.package;
       }
     }
@@ -532,12 +558,15 @@
 
 /// Checks whether [longerPath] begins with [parentPath].
 ///
-/// Skips checking the [start] first characters which are assumed to
-/// already have been matched.
-bool _beginsWith(int start, String parentPath, String longerPath) {
-  if (longerPath.length < parentPath.length) return false;
-  for (var i = start; i < parentPath.length; i++) {
-    if (longerPath.codeUnitAt(i) != parentPath.codeUnitAt(i)) return false;
+/// Skips checking the [parentStart] first characters which are assumed to
+/// already have been matched up to [longerStart].
+bool _beginsWith(
+    int parentStart, String parentPath, int longerStart, String longerPath) {
+  if (longerPath.length - longerStart < parentPath.length - parentStart) {
+    return false;
+  }
+  for (var i = parentStart, j = longerStart; i < parentPath.length; i++, j++) {
+    if (longerPath.codeUnitAt(j) != parentPath.codeUnitAt(i)) return false;
   }
   return true;
 }
diff --git a/lib/src/packages_file.dart b/lib/src/packages_file.dart
index f84db10..6590b03 100644
--- a/lib/src/packages_file.dart
+++ b/lib/src/packages_file.dart
@@ -107,6 +107,13 @@
           'Package URI as location for package', source, separatorIndex + 1));
       continue;
     }
+    if (packageLocation.scheme.startsWith(dartMacroSchemePrefix)) {
+      onError(PackageConfigFormatException(
+          'Macro-generated URI as location for package',
+          source,
+          separatorIndex + 1));
+      continue;
+    }
     var path = packageLocation.path;
     if (!path.endsWith('/')) {
       path += '/';
@@ -120,6 +127,8 @@
     var rootUri = packageLocation;
     if (path.endsWith('/lib/')) {
       // Assume default Pub package layout. Include package itself in root.
+      // TODO(lrn): Stop doing this. Expect the package file to add a root
+      // if it wants a root.
       rootUri =
           packageLocation.replace(path: path.substring(0, path.length - 4));
     }
diff --git a/lib/src/util.dart b/lib/src/util.dart
index 3bf1bec..97129be 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -40,14 +40,39 @@
   return -1;
 }
 
+/// Dart macro generated augmentation file scheme for a package library.
+const dartMacroSchemePrefix = 'dart-macro+';
+
+/// Dart macro generated augmentation file scheme for a package library.
+const dartMacroPackageScheme = 'dart-macro+package';
+
+/// Dart macro generated augmentation file scheme for a file library.
+const dartMacroFileScheme = 'dart-macro+file';
+
+Map<String, String> _macroSchemeTranslation = {
+  dartMacroPackageScheme: 'package',
+  dartMacroFileScheme: 'file',
+};
+
+/// Convert a `dart-macro+` scheme to the underlying scheme.
+String schemeFromMacroScheme(String macroScheme) {
+  assert(macroScheme.startsWith(dartMacroSchemePrefix));
+  return _macroSchemeTranslation[macroScheme] ??=
+      macroScheme.substring(dartMacroSchemePrefix.length);
+}
+
 /// Validate that a [Uri] is a valid `package:` URI.
 ///
 /// Used to validate user input.
 ///
 /// Returns the package name extracted from the package URI,
 /// which is the path segment between `package:` and the first `/`.
-String checkValidPackageUri(Uri packageUri, String name) {
-  if (packageUri.scheme != 'package') {
+///
+/// If [allowMacro] is `true`, the scheme may also be `dart-macro+package`.
+String checkValidPackageUri(Uri packageUri, String name,
+    {bool allowMacro = false}) {
+  if (!packageUri.isScheme('package') &&
+      !(allowMacro && packageUri.isScheme(dartMacroPackageScheme))) {
     throw PackageConfigArgumentError(packageUri, name, 'Not a package: URI');
   }
   if (packageUri.hasAuthority) {
@@ -81,7 +106,7 @@
   if (badIndex >= 0) {
     if (packageName.isEmpty) {
       throw PackageConfigArgumentError(
-          packageUri, name, 'Package names mus be non-empty');
+          packageUri, name, 'Package names must be non-empty');
     }
     if (badIndex == packageName.length) {
       throw PackageConfigArgumentError(packageUri, name,
diff --git a/pubspec.yaml b/pubspec.yaml
index 6ac7f54..51df8bb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: package_config
-version: 2.1.1-wip
+version: 2.2.0-wip
 description: Support for reading and writing Dart Package Configuration files.
 repository: https://github.com/dart-lang/package_config
 
diff --git a/test/package_config_impl_test.dart b/test/package_config_impl_test.dart
index 87d1fd4..f683ba1 100644
--- a/test/package_config_impl_test.dart
+++ b/test/package_config_impl_test.dart
@@ -183,6 +183,63 @@
       'extra': 'data',
     });
   });
+
+  group('macro-generated', () {
+    const macroPrefix = 'dart-macro+';
+    var config = PackageConfig([
+      Package('foo', Uri.parse('file:///pkg/foo/'),
+          packageUriRoot: Uri.parse('file:///pkg/foo/lib/'),
+          languageVersion: LanguageVersion(3, 6)),
+      Package('bar', Uri.parse('file:///pkg/bar/'),
+          packageUriRoot: Uri.parse('file:///pkg/bar/lib/')),
+    ]);
+    test('package URI', () {
+      var uri = Uri.parse('${macroPrefix}package:foo/bar.dart');
+      // The macro-package-URI belongs to the package
+      // of the underlying `package:` URI.
+      var packageOf = config.packageOf(uri);
+      expect(packageOf, isNotNull);
+      expect(packageOf!.name, 'foo');
+
+      // The URI is not a package URI.
+      expect(() => config.resolve(uri), throwsArgumentError);
+
+      // And it has no corresponding package URI.
+      var toPackageUri = config.toPackageUri(uri);
+      expect(toPackageUri, null);
+    });
+
+    test('non-package URI outside lib/', () {
+      var uri = Uri.parse('${macroPrefix}file:///pkg/foo/tool/tool.dart');
+
+      var packageOf = config.packageOf(uri);
+      expect(packageOf, isNotNull);
+      expect(packageOf!.name, 'foo');
+
+      // The URI is not a package URI.
+      expect(() => config.resolve(uri), throwsArgumentError);
+
+      // And it has no corresponding package URI.
+      var toPackageUri = config.toPackageUri(uri);
+      expect(toPackageUri, null);
+    });
+
+    test('non-package URI inside lib/', () {
+      var uri = Uri.parse('${macroPrefix}file:///pkg/foo/lib/file.dart');
+
+      var packageOf = config.packageOf(uri);
+      expect(packageOf, isNotNull);
+      expect(packageOf!.name, 'foo');
+
+      // The URI is not a package URI.
+      expect(() => config.resolve(uri), throwsArgumentError);
+
+      // And it has no corresponding package URI, even if it
+      // "belongs" inside `lib/`.
+      var toPackageUri = config.toPackageUri(uri);
+      expect(toPackageUri, null);
+    });
+  });
 }
 
 final Matcher throwsPackageConfigError = throwsA(isA<PackageConfigError>());