Version 2.13.0-19.0.dev

Merge commit 'b60e0aa5a10090bce296dbba032350143f0a7dfd' into 'dev'
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index f8ef9b5..9d84c71 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -461,6 +461,7 @@
   FfiCode.EMPTY_STRUCT,
   FfiCode.EMPTY_STRUCT_WARNING,
   FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD,
+  FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY,
   FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER,
   FfiCode.FIELD_INITIALIZER_IN_STRUCT,
   FfiCode.GENERIC_STRUCT_SUBCLASS,
@@ -470,11 +471,13 @@
   FfiCode.MISSING_ANNOTATION_ON_STRUCT_FIELD,
   FfiCode.MISSING_EXCEPTION_VALUE,
   FfiCode.MISSING_FIELD_TYPE_IN_STRUCT,
+  FfiCode.MISSING_SIZE_ANNOTATION_CARRAY,
   FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
   FfiCode.MUST_BE_A_SUBTYPE,
   FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
   FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING,
   FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
+  FfiCode.NON_SIZED_TYPE_ARGUMENT,
   FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS,
   FfiCode.SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS,
   FfiCode.SUBTYPE_OF_FFI_CLASS_IN_WITH,
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
index 250c01c..b44213b 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
@@ -53,6 +53,14 @@
   /**
    * No parameters.
    */
+  static const FfiCode EXTRA_SIZE_ANNOTATION_CARRAY = FfiCode(
+      name: 'EXTRA_SIZE_ANNOTATION_CARRAY',
+      message: "'CArray's must have exactly one 'CArraySize' annotation.",
+      correction: "Try removing the extra annotation.");
+
+  /**
+   * No parameters.
+   */
   static const FfiCode FIELD_IN_STRUCT_WITH_INITIALIZER = FfiCode(
       name: 'FIELD_IN_STRUCT_WITH_INITIALIZER',
       message: "Fields in subclasses of 'Struct' can't have initializers.",
@@ -97,9 +105,11 @@
       name: 'INVALID_FIELD_TYPE_IN_STRUCT',
       message:
           "Fields in struct classes can't have the type '{0}'. They can only "
-          "be declared as 'int', 'double', 'Pointer', or subtype of 'Struct'.",
+          "be declared as 'int', 'double', 'CArray', 'Pointer', or subtype of "
+          "'Struct'.",
       correction:
-          "Try using 'int', 'double', 'Pointer', or subtype of 'Struct'.");
+          "Try using 'int', 'double', 'CArray', 'Pointer', or subtype of "
+          "'Struct'.");
 
   /**
    * No parameters.
@@ -143,6 +153,14 @@
       correction: "Try using 'int', 'double' or 'Pointer'.");
 
   /**
+   * No parameters.
+   */
+  static const FfiCode MISSING_SIZE_ANNOTATION_CARRAY = FfiCode(
+      name: 'MISSING_SIZE_ANNOTATION_CARRAY',
+      message: "'CArray's must have exactly one 'CArraySize' annotation.",
+      correction: "Try adding a 'CArraySize' annotation.");
+
+  /**
    * Parameters:
    * 0: the type that should be a valid dart:ffi native type.
    * 1: the name of the function whose invocation depends on this relationship
@@ -202,6 +220,19 @@
 
   /**
    * Parameters:
+   * 0: the type of the field
+   */
+  static const FfiCode NON_SIZED_TYPE_ARGUMENT = FfiCode(
+      name: 'NON_SIZED_TYPE_ARGUMENT',
+      message:
+          "Type arguments to '{0}' can't have the type '{1}'. They can only "
+          "be declared as native integer, 'Float', 'Double', 'Pointer', or "
+          "subtype of Struct'.",
+      correction: "Try using a native integer, 'Float', 'Double', 'Pointer', "
+          "or subtype of 'Struct'.");
+
+  /**
+   * Parameters:
    * 0: the name of the subclass
    * 1: the name of the class being extended, implemented, or mixed in
    */
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index feca32d..012b9ac 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -17,6 +17,7 @@
   static const _allocatorClassName = 'Allocator';
   static const _allocateExtensionMethodName = 'call';
   static const _allocatorExtensionName = 'AllocatorAlloc';
+  static const _carrayClassName = 'CArray';
   static const _dartFfiLibraryName = 'dart.ffi';
   static const _opaqueClassName = 'Opaque';
 
@@ -158,7 +159,8 @@
     if (element is MethodElement) {
       var enclosingElement = element.enclosingElement;
       if (enclosingElement is ExtensionElement) {
-        if (_isNativeStructPointerExtension(enclosingElement)) {
+        if (_isNativeStructPointerExtension(enclosingElement) ||
+            _isNativeStructCArrayExtension(enclosingElement)) {
           if (element.name == '[]') {
             _validateRefIndexed(node);
           }
@@ -242,6 +244,12 @@
       element.name == _allocatorExtensionName &&
       element.library?.name == _dartFfiLibraryName;
 
+  /// Return `true` if the given [element] represents the class `CArray`.
+  bool _isCArray(Element? element) =>
+      element != null &&
+      element.name == _carrayClassName &&
+      element.library?.name == _dartFfiLibraryName;
+
   /// Return `true` if the [typeName] is the name of a type from `dart:ffi`.
   bool _isDartFfiClass(TypeName typeName) =>
       _isDartFfiElement(typeName.name.staticElement);
@@ -274,6 +282,8 @@
         structFieldCount++;
       } else if (_isStructClass(declaredType)) {
         structFieldCount++;
+      } else if (_isCArray(declaredType.element)) {
+        structFieldCount++;
       }
     }
     return structFieldCount == 0;
@@ -301,6 +311,9 @@
       element.name == 'NativeFunctionPointer' &&
       element.library?.name == _dartFfiLibraryName;
 
+  bool _isNativeStructCArrayExtension(Element element) =>
+      element.name == 'StructCArray' && element.library?.name == 'dart.ffi';
+
   bool _isNativeStructPointerExtension(Element element) =>
       element.name == 'StructPointer' && element.library?.name == 'dart.ffi';
 
@@ -348,6 +361,29 @@
     return false;
   }
 
+  /// Returns `true` if [nativeType] is a C type that has a size.
+  bool _isSized(DartType nativeType) {
+    switch (_primitiveNativeType(nativeType)) {
+      case _PrimitiveDartType.double:
+        return true;
+      case _PrimitiveDartType.int:
+        return true;
+      case _PrimitiveDartType.void_:
+        return false;
+      case _PrimitiveDartType.handle:
+        return false;
+      case _PrimitiveDartType.none:
+        break;
+    }
+    if (_isStructClass(nativeType)) {
+      return true;
+    }
+    if (_isPointer(nativeType.element)) {
+      return true;
+    }
+    return false;
+  }
+
   /// Returns `true` iff [nativeType] is a struct type.
   bool _isStructClass(DartType nativeType) {
     if (nativeType is InterfaceType) {
@@ -389,12 +425,14 @@
           nativeType.optionalParameterTypes.isNotEmpty) {
         return false;
       }
-      if (!_isValidFfiNativeType(nativeType.returnType, true, false)) {
+      if (!_isValidFfiNativeType(nativeType.returnType,
+          allowVoid: true, allowEmptyStruct: false)) {
         return false;
       }
 
       for (final DartType typeArg in nativeType.normalParameterTypes) {
-        if (!_isValidFfiNativeType(typeArg, false, false)) {
+        if (!_isValidFfiNativeType(typeArg,
+            allowVoid: false, allowEmptyStruct: false)) {
           return false;
         }
       }
@@ -404,9 +442,10 @@
   }
 
   /// Validates that the given [nativeType] is a valid dart:ffi native type.
-  // TODO(https://dartbug.com/44747): Change to named arguments.
-  bool _isValidFfiNativeType(
-      DartType? nativeType, bool allowVoid, bool allowEmptyStruct) {
+  bool _isValidFfiNativeType(DartType? nativeType,
+      {bool allowVoid = false,
+      bool allowEmptyStruct = false,
+      bool allowCArray = false}) {
     if (nativeType is InterfaceType) {
       // Is it a primitive integer/double type (or ffi.Void if we allow it).
       final primitiveType = _primitiveNativeType(nativeType);
@@ -419,7 +458,8 @@
       }
       if (_isPointerInterfaceType(nativeType)) {
         final nativeArgumentType = nativeType.typeArguments.single;
-        return _isValidFfiNativeType(nativeArgumentType, true, true) ||
+        return _isValidFfiNativeType(nativeArgumentType,
+                allowVoid: true, allowEmptyStruct: true) ||
             _isStructClass(nativeArgumentType) ||
             _isNativeTypeInterfaceType(nativeArgumentType);
       }
@@ -436,6 +476,10 @@
       if (_isOpaqueClass(nativeType)) {
         return true;
       }
+      if (allowCArray && _isCArray(nativeType.element)) {
+        return _isValidFfiNativeType(nativeType.typeArguments.single,
+            allowVoid: false, allowEmptyStruct: false);
+      }
     } else if (nativeType is FunctionType) {
       return _isValidFfiNativeFunctionType(nativeType);
     }
@@ -484,7 +528,8 @@
       return;
     }
     final DartType dartType = typeArgumentTypes[0];
-    if (!_isValidFfiNativeType(dartType, true, true)) {
+    if (!_isValidFfiNativeType(dartType,
+        allowVoid: true, allowEmptyStruct: true)) {
       final AstNode errorNode = node;
       _errorReporter.reportErrorForNode(
           FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
@@ -647,7 +692,7 @@
         targetType.typeArguments.length == 1) {
       final DartType T = targetType.typeArguments[0];
 
-      if (!_isValidFfiNativeType(T, true, true)) {
+      if (!_isValidFfiNativeType(T, allowVoid: true, allowEmptyStruct: true)) {
         final AstNode errorNode = node;
         _errorReporter.reportErrorForNode(
             FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING,
@@ -677,6 +722,13 @@
         _validateAnnotations(fieldType, annotations, _PrimitiveDartType.double);
       } else if (_isPointer(declaredType.element)) {
         _validateNoAnnotations(annotations);
+      } else if (_isCArray(declaredType.element)) {
+        final typeArg = (declaredType as InterfaceType).typeArguments.single;
+        if (!_isSized(typeArg)) {
+          _errorReporter.reportErrorForNode(FfiCode.NON_SIZED_TYPE_ARGUMENT,
+              fieldType, [_carrayClassName, typeArg.toString()]);
+        }
+        _validateSizeOfAnnotation(fieldType, annotations);
       } else if (_isStructClass(declaredType)) {
         final clazz = (declaredType as InterfaceType).element;
         if (_isEmptyStruct(clazz)) {
@@ -782,7 +834,8 @@
 
   void _validateRefIndexed(IndexExpression node) {
     var targetType = node.realTarget.staticType;
-    if (!_isValidFfiNativeType(targetType, false, true)) {
+    if (!_isValidFfiNativeType(targetType,
+        allowVoid: false, allowEmptyStruct: true, allowCArray: true)) {
       final AstNode errorNode = node;
       _errorReporter.reportErrorForNode(
           FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['[]']);
@@ -793,7 +846,8 @@
   /// `Pointer<T extends Struct>.ref`.
   void _validateRefPrefixedIdentifier(PrefixedIdentifier node) {
     var targetType = node.prefix.staticType!;
-    if (!_isValidFfiNativeType(targetType, false, true)) {
+    if (!_isValidFfiNativeType(targetType,
+        allowVoid: false, allowEmptyStruct: true)) {
       final AstNode errorNode = node;
       _errorReporter.reportErrorForNode(
           FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['ref']);
@@ -802,7 +856,8 @@
 
   void _validateRefPropertyAccess(PropertyAccess node) {
     var targetType = node.realTarget.staticType;
-    if (!_isValidFfiNativeType(targetType, false, true)) {
+    if (!_isValidFfiNativeType(targetType,
+        allowVoid: false, allowEmptyStruct: true)) {
       final AstNode errorNode = node;
       _errorReporter.reportErrorForNode(
           FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['ref']);
@@ -815,13 +870,41 @@
       return;
     }
     final DartType T = typeArgumentTypes[0];
-    if (!_isValidFfiNativeType(T, true, true)) {
+    if (!_isValidFfiNativeType(T, allowVoid: true, allowEmptyStruct: true)) {
       final AstNode errorNode = node;
       _errorReporter.reportErrorForNode(
           FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING, errorNode, ['sizeOf']);
     }
   }
 
+  /// Validate that the [annotations] include exactly one size annotation. If
+  /// an error is produced that cannot be associated with an annotation,
+  /// associate it with the [errorNode].
+  void _validateSizeOfAnnotation(
+      AstNode errorNode, NodeList<Annotation> annotations) {
+    final ffiSizeAnnotations = annotations.where((annotation) {
+      if (!_isDartFfiElement(annotation.element)) {
+        return false;
+      }
+      if (annotation.element is! ConstructorElement) {
+        return false;
+      }
+      return annotation.element?.enclosingElement?.name == 'CArraySize';
+    }).toList();
+
+    if (ffiSizeAnnotations.isEmpty) {
+      _errorReporter.reportErrorForNode(
+          FfiCode.MISSING_SIZE_ANNOTATION_CARRAY, errorNode);
+    }
+    if (ffiSizeAnnotations.length > 1) {
+      final extraAnnotations = ffiSizeAnnotations.skip(1);
+      for (final annotation in extraAnnotations) {
+        _errorReporter.reportErrorForNode(
+            FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY, annotation);
+      }
+    }
+  }
+
   /// Validate that the given [typeArgument] has a constant value. Return `true`
   /// if a diagnostic was produced because it isn't constant.
   bool _validateTypeArgument(TypeAnnotation typeArgument, String functionName) {
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index c6c83c4..74bcc94 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -688,6 +688,14 @@
   const DartRepresentationOf(String nativeType);
 }
 
+class CArray<T extends NativeType> extends NativeType {}
+
+class CArraySize {
+  final int numberOfElements;
+
+  const CArraySize(this.numberOfElements);
+}
+
 extension StructPointer<T extends Struct> on Pointer<T> {
   external T get ref;
 
diff --git a/pkg/analyzer/test/src/diagnostics/extra_size_annotation_carray_test.dart b/pkg/analyzer/test/src/diagnostics/extra_size_annotation_carray_test.dart
new file mode 100644
index 0000000..e39e560
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/extra_size_annotation_carray_test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/dart/error/ffi_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ExtraSizeAnnotationCArray);
+  });
+}
+
+@reflectiveTest
+class ExtraSizeAnnotationCArray extends PubPackageResolutionTest {
+  test_one() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @CArraySize(8)
+  CArray<Uint8> a0;
+}
+''');
+  }
+
+  test_two() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @CArraySize(8)
+  @CArraySize(8)
+  CArray<Uint8> a0;
+}
+''', [
+      error(FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY, 64, 14),
+    ]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/missing_size_annotation_carray_test.dart b/pkg/analyzer/test/src/diagnostics/missing_size_annotation_carray_test.dart
new file mode 100644
index 0000000..1bf9a69
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/missing_size_annotation_carray_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/dart/error/ffi_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(MissingSizeAnnotationCArray);
+  });
+}
+
+@reflectiveTest
+class MissingSizeAnnotationCArray extends PubPackageResolutionTest {
+  test_one() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @CArraySize(8)
+  CArray<Uint8> a0;
+}
+''');
+  }
+
+  test_two() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  CArray<Uint8> a0;
+}
+''', [
+      error(FfiCode.MISSING_SIZE_ANNOTATION_CARRAY, 47, 13),
+    ]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart b/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart
new file mode 100644
index 0000000..2a14fcc
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/dart/error/ffi_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(NonSizedTypeArgument);
+  });
+}
+
+@reflectiveTest
+class NonSizedTypeArgument extends PubPackageResolutionTest {
+  test_one() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @CArraySize(8)
+  CArray<Uint8> a0;
+}
+''');
+  }
+
+  test_two() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @CArraySize(8)
+  CArray<CArray<Uint8>> a0;
+}
+''', [
+      error(FfiCode.NON_SIZED_TYPE_ARGUMENT, 64, 21),
+    ]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 49d3869..8b1a3b1 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -180,6 +180,7 @@
 import 'extra_annotation_on_struct_field_test.dart'
     as extra_annotation_on_struct_field;
 import 'extra_positional_arguments_test.dart' as extra_positional_arguments;
+import 'extra_size_annotation_carray_test.dart' as extra_size_annotation_carray;
 import 'field_in_struct_with_initializer_test.dart'
     as field_in_struct_with_initializer;
 import 'field_initialized_by_multiple_initializers_test.dart'
@@ -376,6 +377,8 @@
 import 'missing_js_lib_annotation_test.dart' as missing_js_lib_annotation;
 import 'missing_required_param_test.dart' as missing_required_param;
 import 'missing_return_test.dart' as missing_return;
+import 'missing_size_annotation_carray_test.dart'
+    as missing_size_annotation_carray;
 import 'mixin_application_not_implemented_interface_test.dart'
     as mixin_application_not_implemented_interface;
 import 'mixin_class_declares_constructor_test.dart'
@@ -451,6 +454,7 @@
 import 'non_native_function_type_argument_to_pointer_test.dart'
     as non_native_function_type_argument_to_pointer;
 import 'non_null_opt_out_test.dart' as non_null_opt_out;
+import 'non_sized_type_argument_test.dart' as non_sized_type_argument;
 import 'non_type_as_type_argument_test.dart' as non_type_as_type_argument;
 import 'non_type_in_catch_clause_test.dart' as non_type_in_catch_clause;
 import 'non_void_return_for_operator_test.dart' as non_void_return_for_operator;
@@ -789,6 +793,7 @@
     external_variable_initializer.main();
     extra_annotation_on_struct_field.main();
     extra_positional_arguments.main();
+    extra_size_annotation_carray.main();
     field_in_struct_with_initializer.main();
     field_initialized_by_multiple_initializers.main();
     final_initialized_in_declaration_and_constructor.main();
@@ -912,6 +917,7 @@
     missing_js_lib_annotation.main();
     missing_required_param.main();
     missing_return.main();
+    missing_size_annotation_carray.main();
     mixin_application_not_implemented_interface.main();
     mixin_class_declares_constructor.main();
     mixin_declares_constructor.main();
@@ -961,6 +967,7 @@
     non_generative_implicit_constructor.main();
     non_native_function_type_argument_to_pointer.main();
     non_null_opt_out.main();
+    non_sized_type_argument.main();
     non_type_as_type_argument.main();
     non_type_in_catch_clause.main();
     non_void_return_for_operator.main();
diff --git a/tools/VERSION b/tools/VERSION
index c79fbca..ffe0133 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 18
+PRERELEASE 19
 PRERELEASE_PATCH 0
\ No newline at end of file