[analyzer/ffi] Support `Union`s

This CL does not add unions to `dart:ffi` yet, only to the mock
SDK in the analyzer for testing the analyzer. This means we can review
and land it separately.

Also, this CL fixes some tests to not use nullable fields in Structs.

Bug: https://github.com/dart-lang/sdk/issues/38491

Change-Id: Ia972f31475859aa57e012adf39b636919995b183
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194788
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
index dc8e106..c015c40 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
@@ -52,7 +52,8 @@
    */
   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.",
+      message:
+          "Fields in subclasses of 'Struct' and 'Union' can't have initializers.",
       correction:
           "Try removing the initializer and marking the field as external.");
 
@@ -61,7 +62,8 @@
    */
   static const FfiCode FIELD_INITIALIZER_IN_STRUCT = FfiCode(
       name: 'FIELD_INITIALIZER_IN_STRUCT',
-      message: "Constructors in subclasses of 'Struct' can't have field "
+      message:
+          "Constructors in subclasses of 'Struct' and 'Union' can't have field "
           "initializers.",
       correction: "Try removing the field initializer and marking the field as"
           " external.");
@@ -72,7 +74,8 @@
    */
   static const FfiCode GENERIC_STRUCT_SUBCLASS = FfiCode(
       name: 'GENERIC_STRUCT_SUBCLASS',
-      message: "The class '{0}' can't extend 'Struct' because it is generic.",
+      message:
+          "The class '{0}' can't extend 'Struct' or 'Union' because it is generic.",
       correction: "Try removing the type parameters from '{0}'.");
 
   /**
@@ -95,10 +98,10 @@
       message:
           "Fields in struct classes can't have the type '{0}'. They can only "
           "be declared as 'int', 'double', 'Array', 'Pointer', or subtype of "
-          "'Struct'.",
+          "'Struct' or 'Union'.",
       correction:
           "Try using 'int', 'double', 'Array', 'Pointer', or subtype of "
-          "'Struct'.");
+          "'Struct' or 'Union'.");
 
   /**
    * No parameters.
@@ -202,9 +205,9 @@
       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'.",
+          "subtype of 'Struct' or 'Union'.",
       correction: "Try using a native integer, 'Float', 'Double', 'Pointer', "
-          "or subtype of 'Struct'.");
+          "or subtype of 'Struct' or 'Union'.");
 
   /**
    * No parameters.
@@ -252,7 +255,7 @@
   static const FfiCode SUBTYPE_OF_FFI_CLASS_IN_EXTENDS = FfiCode(
     name: 'SUBTYPE_OF_FFI_CLASS',
     message: "The class '{0}' can't extend '{1}'.",
-    correction: "Try extending 'Struct'.",
+    correction: "Try extending 'Struct' or 'Union'.",
     uniqueName: 'SUBTYPE_OF_FFI_CLASS_IN_EXTENDS',
   );
 
@@ -264,7 +267,7 @@
   static const FfiCode SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS = FfiCode(
     name: 'SUBTYPE_OF_FFI_CLASS',
     message: "The class '{0}' can't implement '{1}'.",
-    correction: "Try extending 'Struct'.",
+    correction: "Try extending 'Struct' or 'Union'.",
     uniqueName: 'SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS',
   );
 
@@ -276,7 +279,7 @@
   static const FfiCode SUBTYPE_OF_FFI_CLASS_IN_WITH = FfiCode(
     name: 'SUBTYPE_OF_FFI_CLASS',
     message: "The class '{0}' can't mix in '{1}'.",
-    correction: "Try extending 'Struct'.",
+    correction: "Try extending 'Struct' or 'Union'.",
     uniqueName: 'SUBTYPE_OF_FFI_CLASS_IN_WITH',
   );
 
@@ -288,8 +291,8 @@
   static const FfiCode SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS = FfiCode(
     name: 'SUBTYPE_OF_STRUCT_CLASS',
     message: "The class '{0}' can't extend '{1}' because '{1}' is a subtype of "
-        "'Struct'.",
-    correction: "Try extending 'Struct' directly.",
+        "'Struct' or 'Union'.",
+    correction: "Try extending 'Struct' or 'Union' directly.",
     uniqueName: 'SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS',
   );
 
@@ -302,8 +305,8 @@
     name: 'SUBTYPE_OF_STRUCT_CLASS',
     message:
         "The class '{0}' can't implement '{1}' because '{1}' is a subtype of "
-        "'Struct'.",
-    correction: "Try extending 'Struct' directly.",
+        "'Struct' or 'Union'.",
+    correction: "Try extending 'Struct' or 'Union' directly.",
     uniqueName: 'SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS',
   );
 
@@ -315,8 +318,8 @@
   static const FfiCode SUBTYPE_OF_STRUCT_CLASS_IN_WITH = FfiCode(
     name: 'SUBTYPE_OF_STRUCT_CLASS',
     message: "The class '{0}' can't mix in '{1}' because '{1}' is a subtype of "
-        "'Struct'.",
-    correction: "Try extending 'Struct' directly.",
+        "'Struct' or 'Union'.",
+    correction: "Try extending 'Struct' or 'Union' directly.",
     uniqueName: 'SUBTYPE_OF_STRUCT_CLASS_IN_WITH',
   );
 
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 694d0ad..f330413 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -41,6 +41,8 @@
 
   static const _structClassName = 'Struct';
 
+  static const _unionClassName = 'Union';
+
   /// The type system used to check types.
   final TypeSystemImpl typeSystem;
 
@@ -49,18 +51,18 @@
 
   /// A flag indicating whether we are currently visiting inside a subclass of
   /// `Struct`.
-  bool inStruct = false;
+  bool inCompound = false;
 
-  /// Subclass of `Struct` we are currently visiting, or `null`.
-  ClassDeclaration? struct;
+  /// Subclass of `Struct` or `Union` we are currently visiting, or `null`.
+  ClassDeclaration? compound;
 
   /// Initialize a newly created verifier.
   FfiVerifier(this.typeSystem, this._errorReporter);
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
-    inStruct = false;
-    struct = null;
+    inCompound = false;
+    compound = null;
     // Only the Allocator, Opaque and Struct class may be extended.
     var extendsClause = node.extendsClause;
     if (extendsClause != null) {
@@ -68,14 +70,16 @@
       final ffiClass = superclass.ffiClass;
       if (ffiClass != null) {
         final className = ffiClass.name;
-        if (className == _structClassName) {
-          inStruct = true;
-          struct = node;
+        if (className == _structClassName || className == _unionClassName) {
+          inCompound = true;
+          compound = node;
           if (node.declaredElement!.isEmptyStruct) {
             _errorReporter
                 .reportErrorForNode(FfiCode.EMPTY_STRUCT, node, [node.name]);
           }
-          _validatePackedAnnotation(node.metadata);
+          if (className == _structClassName) {
+            _validatePackedAnnotation(node.metadata);
+          }
         } else if (className != _allocatorClassName &&
             className != _opaqueClassName) {
           _errorReporter.reportErrorForNode(
@@ -83,7 +87,7 @@
               superclass.name,
               [node.name.name, superclass.name.name]);
         }
-      } else if (superclass.isStructSubtype) {
+      } else if (superclass.isCompoundSubtype) {
         _errorReporter.reportErrorForNode(
             FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS,
             superclass,
@@ -101,7 +105,7 @@
       if (typename.ffiClass != null) {
         _errorReporter.reportErrorForNode(
             subtypeOfFfiCode, typename, [node.name, typename.name]);
-      } else if (typename.isStructSubtype) {
+      } else if (typename.isCompoundSubtype) {
         _errorReporter.reportErrorForNode(
             subtypeOfStructCode, typename, [node.name, typename.name]);
       }
@@ -122,7 +126,7 @@
       }
     }
 
-    if (inStruct && node.declaredElement!.typeParameters.isNotEmpty) {
+    if (inCompound && node.declaredElement!.typeParameters.isNotEmpty) {
       _errorReporter.reportErrorForNode(
           FfiCode.GENERIC_STRUCT_SUBCLASS, node.name, [node.name]);
     }
@@ -131,7 +135,7 @@
 
   @override
   void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
-    if (inStruct) {
+    if (inCompound) {
       _errorReporter.reportErrorForNode(
           FfiCode.FIELD_INITIALIZER_IN_STRUCT, node);
     }
@@ -140,8 +144,8 @@
 
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
-    if (inStruct) {
-      _validateFieldsInStruct(node);
+    if (inCompound) {
+      _validateFieldsInCompound(node);
     }
     super.visitFieldDeclaration(node);
   }
@@ -248,7 +252,7 @@
       case _PrimitiveDartType.none:
         break;
     }
-    if (nativeType.isStructSubtype) {
+    if (nativeType.isCompoundSubtype) {
       return true;
     }
     if (nativeType.isPointer) {
@@ -311,10 +315,10 @@
         final nativeArgumentType = nativeType.typeArguments.single;
         return _isValidFfiNativeType(nativeArgumentType,
                 allowVoid: true, allowEmptyStruct: true, allowHandle: true) ||
-            nativeArgumentType.isStructSubtype ||
+            nativeArgumentType.isCompoundSubtype ||
             nativeArgumentType.isNativeType;
       }
-      if (nativeType.isStructSubtype) {
+      if (nativeType.isCompoundSubtype) {
         if (!allowEmptyStruct) {
           if (nativeType.element.isEmptyStruct) {
             // TODO(dartbug.com/36780): This results in an error message not
@@ -548,8 +552,8 @@
   }
 
   /// Validate that the fields declared by the given [node] meet the
-  /// requirements for fields within a struct class.
-  void _validateFieldsInStruct(FieldDeclaration node) {
+  /// requirements for fields within a struct or union class.
+  void _validateFieldsInCompound(FieldDeclaration node) {
     if (node.isStatic) {
       return;
     }
@@ -576,18 +580,18 @@
         final arrayDimensions = declaredType.arrayDimensions;
         _validateSizeOfAnnotation(fieldType, annotations, arrayDimensions);
         final arrayElement = declaredType.arrayElementType;
-        if (arrayElement.isStructSubtype) {
+        if (arrayElement.isCompoundSubtype) {
           final elementClass = (arrayElement as InterfaceType).element;
-          _validatePackingNesting(struct!.declaredElement!, elementClass,
+          _validatePackingNesting(compound!.declaredElement!, elementClass,
               errorNode: fieldType);
         }
-      } else if (declaredType.isStructSubtype) {
+      } else if (declaredType.isCompoundSubtype) {
         final clazz = (declaredType as InterfaceType).element;
         if (clazz.isEmptyStruct) {
           _errorReporter
               .reportErrorForNode(FfiCode.EMPTY_STRUCT, node, [clazz.name]);
         }
-        _validatePackingNesting(struct!.declaredElement!, clazz,
+        _validatePackingNesting(compound!.declaredElement!, clazz,
             errorNode: fieldType);
       } else {
         _errorReporter.reportErrorForNode(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT,
@@ -632,7 +636,7 @@
     if ((FT as FunctionType).returnType.isVoid ||
         R.isPointer ||
         R.isHandle ||
-        R.isStructSubtype) {
+        R.isCompoundSubtype) {
       if (argCount != 1) {
         _errorReporter.reportErrorForNode(
             FfiCode.INVALID_EXCEPTION_VALUE, node.argumentList.arguments[1]);
@@ -975,7 +979,7 @@
         return false;
       } else if (declaredType.isPointer) {
         return false;
-      } else if (declaredType.isStructSubtype) {
+      } else if (declaredType.isCompoundSubtype) {
         return false;
       } else if (declaredType.isArray) {
         return false;
@@ -1087,22 +1091,25 @@
     return false;
   }
 
-  bool get isStruct {
+  bool get isCompound {
     final self = this;
     if (self is InterfaceType) {
       final element = self.element;
-      return element.name == FfiVerifier._structClassName && element.isFfiClass;
+      final name = element.name;
+      return (name == FfiVerifier._structClassName ||
+              name == FfiVerifier._unionClassName) &&
+          element.isFfiClass;
     }
     return false;
   }
 
   /// Returns `true` if this is a struct type, i.e. a subtype of `Struct`.
-  bool get isStructSubtype {
+  bool get isCompoundSubtype {
     final self = this;
     if (self is InterfaceType) {
       final superType = self.element.supertype;
       if (superType != null) {
-        return superType.isStruct;
+        return superType.isCompound;
       }
     }
     return false;
@@ -1115,17 +1122,17 @@
     return name.staticElement.ffiClass;
   }
 
-  /// Return `true` if this represents a subtype of `Struct`.
-  bool get isStructSubtype {
+  /// Return `true` if this represents a subtype of `Struct` or `Union`.
+  bool get isCompoundSubtype {
     var element = name.staticElement;
     if (element is ClassElement) {
-      bool isStruct(InterfaceType? type) {
-        return type != null && type.isStruct;
+      bool isCompound(InterfaceType? type) {
+        return type != null && type.isCompound;
       }
 
-      return isStruct(element.supertype) ||
-          element.interfaces.any(isStruct) ||
-          element.mixins.any(isStruct);
+      return isCompound(element.supertype) ||
+          element.interfaces.any(isCompound) ||
+          element.mixins.any(isCompound);
     }
     return false;
   }
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index 8c3a9a5..d2def94 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -670,12 +670,18 @@
       [Object exceptionalReturn]) {}
 }
 
+final Pointer<Never> nullptr = Pointer.fromAddress(0);
+
 extension NativeFunctionPointer<NF extends Function>
     on Pointer<NativeFunction<NF>> {
   external DF asFunction<DF extends Function>();
 }
 
-class Struct extends NativeType {}
+class _Compound extends NativeType {}
+
+class Struct extends _Compound {}
+
+class Union extends _Compound {}
 
 class Packed {
   final int memberAlignment;
diff --git a/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart b/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart
index 5a32780..426ff14 100644
--- a/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart
@@ -19,10 +19,21 @@
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
-  Pointer? p = null;
+  Pointer p = nullptr;
 }
 ''', [
-      error(FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER, 55, 1),
+      error(FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER, 54, 1),
+    ]);
+  }
+
+  test_instance_withInitializer2() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class C extends Union {
+  Pointer p = nullptr;
+}
+''', [
+      error(FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER, 53, 1),
     ]);
   }
 
diff --git a/pkg/analyzer/test/src/diagnostics/field_initializer_in_struct_test.dart b/pkg/analyzer/test/src/diagnostics/field_initializer_in_struct_test.dart
index e687a98..dd2978f 100644
--- a/pkg/analyzer/test/src/diagnostics/field_initializer_in_struct_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/field_initializer_in_struct_test.dart
@@ -19,11 +19,23 @@
     await assertErrorsInCode('''
 import 'dart:ffi';
 class C extends Struct {
-  @Int32() int? f;
+  @Int32() int f;
   C() : f = 0;
 }
 ''', [
-      error(FfiCode.FIELD_INITIALIZER_IN_STRUCT, 71, 5),
+      error(FfiCode.FIELD_INITIALIZER_IN_STRUCT, 70, 5),
+    ]);
+  }
+
+  test_fieldInitializer2() async {
+    await assertErrorsInCode('''
+import 'dart:ffi';
+class C extends Union {
+  @Int32() int f;
+  C() : f = 0;
+}
+''', [
+      error(FfiCode.FIELD_INITIALIZER_IN_STRUCT, 69, 5),
     ]);
   }
 
diff --git a/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart b/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart
index 710a812..68e338d 100644
--- a/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart
@@ -19,7 +19,18 @@
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 class S<T> extends Struct {
-  Pointer notEmpty;
+  external Pointer notEmpty;
+}
+''', [
+      error(FfiCode.GENERIC_STRUCT_SUBCLASS, 25, 1),
+    ]);
+  }
+
+  test_genericUnion() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class S<T> extends Union {
+  external Pointer notEmpty;
 }
 ''', [
       error(FfiCode.GENERIC_STRUCT_SUBCLASS, 25, 1),
@@ -30,7 +41,7 @@
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 class S extends Struct {
-  Pointer notEmpty;
+  external Pointer notEmpty;
 }
 ''');
   }
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart
index 4cdedd7..4343b90 100644
--- a/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart
@@ -20,12 +20,26 @@
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
-  String? str;
+  external String str;
 
-  Pointer? notEmpty;
+  external Pointer notEmpty;
 }
 ''', [
-      error(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT, 46, 7),
+      error(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT, 55, 6),
+    ]);
+  }
+
+  // TODO(https://dartbug.com/44677): Remove Pointer notEmpty field.
+  test_instance_invalid2() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class C extends Union {
+  external String str;
+
+  external Pointer notEmpty;
+}
+''', [
+      error(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT, 54, 6),
     ]);
   }
 
@@ -33,19 +47,18 @@
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
-  Pointer? p;
+  external Pointer p;
 }
 ''');
   }
 
-  // TODO(https://dartbug.com/44677): Remove Pointer notEmpty field.
   test_static() async {
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
   static String? str;
 
-  Pointer? notEmpty;
+  external Pointer notEmpty;
 }
 ''');
   }
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
index ec3f26d..c06084a 100644
--- a/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart
@@ -15,27 +15,40 @@
 
 @reflectiveTest
 class NonSizedTypeArgument extends PubPackageResolutionTest {
-  test_one() async {
-    await assertNoErrorsInCode(r'''
-import 'dart:ffi';
-
-class C extends Struct {
-  @Array(8)
-  Array<Uint8> a0;
-}
-''');
-  }
-
-  test_two() async {
+  test_invalid_struct() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 
 class C extends Struct {
   @Array(8)
-  Array<Void> a0;
+  external Array<Void> a0;
 }
 ''', [
-      error(FfiCode.NON_SIZED_TYPE_ARGUMENT, 59, 11),
+      error(FfiCode.NON_SIZED_TYPE_ARGUMENT, 68, 11),
     ]);
   }
+
+  test_invalid_union() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Union {
+  @Array(8)
+  external Array<Void> a0;
+}
+''', [
+      error(FfiCode.NON_SIZED_TYPE_ARGUMENT, 67, 11),
+    ]);
+  }
+
+  test_valid() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @Array(8)
+  external Array<Uint8> a0;
+}
+''');
+  }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/packed_annotation_test.dart b/pkg/analyzer/test/src/diagnostics/packed_annotation_test.dart
index 21a5454..88a67db 100644
--- a/pkg/analyzer/test/src/diagnostics/packed_annotation_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/packed_annotation_test.dart
@@ -16,7 +16,7 @@
 
 @reflectiveTest
 class PackedAnnotation extends PubPackageResolutionTest {
-  test_error_1() async {
+  test_error_double() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 
@@ -31,7 +31,7 @@
   }
 
   /// Regress test for http://dartbug.com/45498.
-  test_error_2() async {
+  test_error_missing() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 
@@ -45,7 +45,7 @@
     ]);
   }
 
-  test_no_error_1() async {
+  test_no_error_struct_no_annotation() async {
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 
@@ -55,7 +55,7 @@
 ''');
   }
 
-  test_no_error_2() async {
+  test_no_error_struct_one_annotation() async {
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 
@@ -65,4 +65,40 @@
 }
 ''');
   }
+
+  /// Doesn't do anything on Unions.
+  test_no_error_union_no_annotation() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Union {
+  external Pointer<Uint8> notEmpty;
+}
+''');
+  }
+
+  /// Doesn't do anything on Unions.
+  test_no_error_union_one_annotation() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Packed(1)
+class C extends Union {
+  external Pointer<Uint8> notEmpty;
+}
+''');
+  }
+
+  /// Doesn't do anything on Unions.
+  test_no_error_union_two_annotations() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Packed(1)
+@Packed(1)
+class C extends Union {
+  external Pointer<Uint8> notEmpty;
+}
+''');
+  }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart b/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
index 85129db..6e19801 100644
--- a/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
@@ -87,7 +87,7 @@
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
-  Pointer notEmpty;
+  external Pointer notEmpty;
 }
 ''');
   }
@@ -128,6 +128,15 @@
     ]);
   }
 
+  test_Union() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+class C extends Union {
+  external Pointer notEmpty;
+}
+''');
+  }
+
   test_Void() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
@@ -248,6 +257,15 @@
     ]);
   }
 
+  test_Union() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class C implements Union {}
+''', [
+      error(FfiCode.SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS, 38, 5),
+    ]);
+  }
+
   test_Void() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
@@ -390,6 +408,16 @@
     ]);
   }
 
+  test_Union() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class C with Union {}
+''', [
+      error(CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT, 32, 5),
+      error(FfiCode.SUBTYPE_OF_FFI_CLASS_IN_WITH, 32, 5),
+    ]);
+  }
+
   test_Void() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
diff --git a/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart b/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart
index 00380d1..ab9e982 100644
--- a/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart
@@ -18,22 +18,34 @@
 
 @reflectiveTest
 class SubtypeOfStructClassInExtendsTest extends PubPackageResolutionTest {
-  test_extends() async {
+  test_extends_struct() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 class S extends Struct {
-  Pointer notEmpty;
+  external Pointer notEmpty;
 }
 class C extends S {}
 ''', [
-      error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, 82, 1),
+      error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, 91, 1),
+    ]);
+  }
+
+  test_extends_union() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class S extends Union {
+  external Pointer notEmpty;
+}
+class C extends S {}
+''', [
+      error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, 90, 1),
     ]);
   }
 }
 
 @reflectiveTest
 class SubtypeOfStructClassInImplementsTest extends PubPackageResolutionTest {
-  test_implements() async {
+  test_implements_struct() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 class S extends Struct {}
@@ -43,11 +55,22 @@
       error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS, 64, 1),
     ]);
   }
+
+  test_implements_union() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class S extends Union {}
+class C implements S {}
+''', [
+      error(FfiCode.EMPTY_STRUCT, 19, 24),
+      error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS, 63, 1),
+    ]);
+  }
 }
 
 @reflectiveTest
 class SubtypeOfStructClassInWithTest extends PubPackageResolutionTest {
-  test_with() async {
+  test_with_struct() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 class S extends Struct {}
@@ -58,4 +81,16 @@
       error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH, 58, 1),
     ]);
   }
+
+  test_with_union() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class S extends Union {}
+class C with S {}
+''', [
+      error(FfiCode.EMPTY_STRUCT, 19, 24),
+      error(CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT, 57, 1),
+      error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH, 57, 1),
+    ]);
+  }
 }