Repository: flutter
Issue: [pigeon] Empty generated Dart classes aren't serializable
Labels: package,team-ecosystem,has reproducible steps,p: pigeon,P2,triaged-ecosystem,found in release: 3.27,found in release: 3.28

### Steps to reproduce

- create `pigeon/messages.dart`:

- Define sealed class `A` 

- Define `B` which extends `A` with some fields

- Define `C` which extends `A` without any fields

- run `flutter pub run pigeon --input ./pigeon/messages.dart`

- get generated file `src/messages.g.dart`

### Expected results

`C.decode`/`C.encode` is generated and we got no errors

### Actual results

`C.decode`/`C.encode` was not generated, however `B.decode`/`B.encode` was.

```dart
// Autogenerated from Pigeon (v22.7.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers

import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;

import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';

PlatformException _createConnectionError(String channelName) {
  return PlatformException(
    code: 'channel-error',
    message: 'Unable to establish connection on channel: "$channelName".',
  );
}

sealed class A {
}

class B extends A {
  B({
    this.number,
    this.message,
  });

  int? number;

  String? message;

  Object encode() {
    return <Object?>[
      number,
      message,
    ];
  }

  static B decode(Object result) {
    result as List<Object?>;
    return B(
      number: result[0] as int?,
      message: result[1] as String?,
    );
  }
}

class C extends A { <----- no decode/encode
}


class _PigeonCodec extends StandardMessageCodec {
  const _PigeonCodec();
  @override
  void writeValue(WriteBuffer buffer, Object? value) {
    if (value is int) {
      buffer.putUint8(4);
      buffer.putInt64(value);
    }    else if (value is B) {
      buffer.putUint8(129);
      writeValue(buffer, value.encode());
    }    else if (value is C) {
      buffer.putUint8(130);
      writeValue(buffer, value.encode()); <----- not generated, error
    } else {
      super.writeValue(buffer, value);
    }
  }

  @override
  Object? readValueOfType(int type, ReadBuffer buffer) {
    switch (type) {
      case 129: 
        return B.decode(readValue(buffer)!);
      case 130: 
        return C.decode(readValue(buffer)!); <----- not generated, error
      default:
        return super.readValueOfType(type, buffer);
    }
  }
}

class SomeHostApi {
  /// Constructor for [SomeHostApi].  The [binaryMessenger] named argument is
  /// available for dependency injection.  If it is left null, the default
  /// BinaryMessenger will be used which routes to the host platform.
  SomeHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
      : pigeonVar_binaryMessenger = binaryMessenger,
        pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
  final BinaryMessenger? pigeonVar_binaryMessenger;

  static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();

  final String pigeonVar_messageChannelSuffix;

  Future<C> getC(C c) async {
    final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeHostApi.getC$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
      pigeonVar_channelName,
      pigeonChannelCodec,
      binaryMessenger: pigeonVar_binaryMessenger,
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_channel.send(<Object?>[c]) as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      return (pigeonVar_replyList[0] as C?)!;
    }
  }

  Future<A> getA(A a) async {
    final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeHostApi.getA$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
      pigeonVar_channelName,
      pigeonChannelCodec,
      binaryMessenger: pigeonVar_binaryMessenger,
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_channel.send(<Object?>[a]) as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      return (pigeonVar_replyList[0] as A?)!;
    }
  }
}

class SomeFlutterApi {
  /// Constructor for [SomeFlutterApi].  The [binaryMessenger] named argument is
  /// available for dependency injection.  If it is left null, the default
  /// BinaryMessenger will be used which routes to the host platform.
  SomeFlutterApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
      : pigeonVar_binaryMessenger = binaryMessenger,
        pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
  final BinaryMessenger? pigeonVar_binaryMessenger;

  static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();

  final String pigeonVar_messageChannelSuffix;

  Future<C> getC(C c) async {
    final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeFlutterApi.getC$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
      pigeonVar_channelName,
      pigeonChannelCodec,
      binaryMessenger: pigeonVar_binaryMessenger,
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_channel.send(<Object?>[c]) as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      return (pigeonVar_replyList[0] as C?)!;
    }
  }

  Future<A> getA(A a) async {
    final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeFlutterApi.getA$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
      pigeonVar_channelName,
      pigeonChannelCodec,
      binaryMessenger: pigeonVar_binaryMessenger,
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_channel.send(<Object?>[a]) as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      return (pigeonVar_replyList[0] as A?)!;
    }
  }
}
```

### Code sample

<details open><summary>Code sample</summary>

```dart
import 'package:pigeon/pigeon.dart';

// #docregion config
@ConfigurePigeon(
  PigeonOptions(
    dartOut: 'lib/src/messages.g.dart',
    dartOptions: DartOptions(),
    kotlinOut:
        'android/src/main/kotlin/com/pigeonsealedexample/pigeon_sealed_plugin/Messages.g.kt',
    kotlinOptions:
        KotlinOptions(package: 'com.pigeonsealedexample.somepackage'),
    swiftOut: 'ios/Classes/Messages.g.swift',
    swiftOptions: SwiftOptions(),
    dartPackageName: 'pigeon_sealed_plugin_example',
  ),
)
sealed class A {
  A();
}

class B extends A {
  final int? number;
  final String? message;

  B({
    required this.number,
    this.message,
  });
}

class C extends A {
  C();
}

@HostApi()
abstract class SomeHostApi {
  C getC(C c);

  A getA(A a);
}

@HostApi()
abstract class SomeFlutterApi {
  C getC(C c);

  A getA(A a);
}

```

</details>


### Screenshots or Video

<details open>
<summary>Screenshots / Video demonstration</summary>

[Upload media here]

</details>


### Logs

<details open><summary>Logs</summary>

```console
Deprecated. Use `dart run` instead.
Building package executable... 
Built pigeon:pigeon.
```

</details>


### Flutter Doctor output

<details open><summary>Doctor output</summary>

```console
Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel master, 3.26.0-1.0.pre.168, on macOS 14.5 23F79 darwin-arm64, locale en-RU)
    ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/3.5.2/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/feduke-nukem/Desktop/git-projects/flutter/flutter. Consider adding
      /Users/feduke-nukem/Desktop/git-projects/flutter/flutter/bin to the front of your path.
    ! Upstream repository https://github.com/feduke-nukem/flutter is not a standard remote.
      Set environment variable "FLUTTER_GIT_URL" to https://github.com/feduke-nukem/flutter to dismiss this error.
[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.3)
[✓] VS Code (version 1.96.0)
[✓] Connected device (6 available)
    ! Error: Browsing on the local area network for iPhone (Анна). Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources
```

</details>