Skip to content

[augmentations] augment version of class declaration involving generics causes incorrect errors elsewhere #60040

@timmaffett

Description

@timmaffett

dart --version

dart --version
Dart SDK version: 3.8.0-24.0.dev (dev) (Wed Jan 22 12:05:43 2025 -0800) on "windows_x64"

When defining the Box<T> class in the code below using augmentation the analyzer produces strange error messages:
The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'.

As you can see in the error message the types match, but the error is produced because of some difference the augment triggers.

Augmentation excerpt that triggers error:

class Box<T> {
  Box(this.label, this.data);

  final String label;
  final T data;
}

augment class Box<T> implements SelfEncodable {
  static const Codable1<Box, dynamic> codable = BoxCodable();

  @override
  void encode(Encoder encoder, [Encodable<T>? encodableT]) {
  }
}

If you instead comment out the augment versions and use the single class definition, then everything works as expected.

Works as expected:

class Box<T> implements SelfEncodable {
  Box(this.label, this.data);

  final String label;
  final T data;

  static const Codable1<Box, dynamic> codable = BoxCodable();

  @override
  void encode(Encoder encoder, [Encodable<T>? encodableT]) {
  }
}

ERROR occurs in this excerpt:

extension BoxEncodableExtension<T> on Box<T> {
  SelfEncodable use([Encodable<T>? encodableT]) {
    return SelfEncodable.fromHandler((e) => encode(e, encodableT));     //! << encodableT  - The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'.
  }
}

class BoxCodable<T> extends Codable1<Box<T>, T> {
  const BoxCodable();

  @override
  void encode(Box<T> value, Encoder encoder, [Encodable<T>? encodableA]) {
    value.encode(encoder, encodableA);   //! << encodableA -  The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'.
  }

  @override
  external Box<T> decode(Decoder decoder, [Decodable<T>? decodableT]);
}

This standalone code was extracted from augment prototypes of the codable RFC I was working on.
 
Complete standalone version that can be used to examine error:
standalone_error.dart

/* 
//!USING `augment` causes analyzer to produce weird errors:

The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'. dartargument_type_not_assignable
interface.dart(89, 26): Encodable is defined in C:\src\codable_workspace\packages\codable\lib\src\core\interface.dart
interface.dart(89, 26): Encodable is defined in C:\src\codable_workspace\packages\codable\lib\src\core\interface.dart
[Encodable<T>? encodableT]
Type: Encodable<T>?

*/
//! Augmentation version produces strange `The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'.`
//!  errors.

class Box<T> {
  Box(this.label, this.data);

  final String label;
  final T data;
}

augment class Box<T> implements SelfEncodable {
  static const Codable1<Box, dynamic> codable = BoxCodable();

  @override
  void encode(Encoder encoder, [Encodable<T>? encodableT]) {
  }
}

/*
//! THIS VERSION without augmentation produces NO ERROR:
class Box<T> implements SelfEncodable {
  Box(this.label, this.data);

  final String label;
  final T data;

  static const Codable1<Box, dynamic> codable = BoxCodable();

  @override
  void encode(Encoder encoder, [Encodable<T>? encodableT]) {
  }
}
//! END VERSION without augmentation produces NO ERROR:
*/


extension BoxEncodableExtension<T> on Box<T> {
  SelfEncodable use([Encodable<T>? encodableT]) {
    return SelfEncodable.fromHandler((e) => encode(e, encodableT));     //! <<The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'.
  }
}

class BoxCodable<T> extends Codable1<Box<T>, T> {
  const BoxCodable();

  @override
  void encode(Box<T> value, Encoder encoder, [Encodable<T>? encodableA]) {
    value.encode(encoder, encodableA);   //! <<The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'.
  }

  @override
  external Box<T> decode(Decoder decoder, [Decodable<T>? decodableT]);
}




// SUPPORTING CLASSES 


abstract interface class Decodable<T> {
  /// Decodes a value of type [T] using the [decoder].
  ///
  /// The implementation should first use [Decoder.whatsNext] to determine the type of the encoded data.
  /// Then it should use one of the [Decoder]s `.decode...()` methods to decode into its target type.
  /// If the returned [DecodingType] is not supported, the implementation can use [Decoder.expect] to throw a detailed error.
  T decode(Decoder decoder);

  /// Creates a [Decodable] from a handler function.
  factory Decodable.fromHandler(T Function(Decoder decoder) decode) => _DecodableFromHandler(decode);
}
abstract interface class Decoder {
}

/// Variant of [Decodable] that decodes a generic value of type [T] with one type parameter [A].
abstract interface class Decodable1<T, A> implements Decodable<T> {
  /// Decodes a value of type [T] using the [decoder].
  ///
  /// The implementation should first use [Decoder.whatsNext] to determine the type of the encoded data.
  /// Then it should use one of the typed [Decoder].decode...() methods to decode into its target type.
  /// If the returned [DecodingType] is not supported, the implementation can use [Decoder.expect] to throw a detailed error.
  ///
  /// The [decodableA] parameter should be used to decode nested values of type [A]. When it is `null` the
  /// implementation may choose to throw an error or use a fallback way of decoding values of type [A].
  @override
  T decode(Decoder decoder, [Decodable<A>? decodableA]);
}


abstract interface class Encodable1<T, A> implements Encodable<T> {
  @override
  void encode(T value, Encoder encoder, [Encodable<A>? encodableA]);
}


abstract class Codable<T> implements Encodable<T>, Decodable<T> {
  const Codable();

  /// Creates a [Codable] from a pair of handler functions.
  factory Codable.fromHandlers({
    required T Function(Decoder decoder) decode,
    required void Function(T value, Encoder encoder) encode,
  }) =>
      _CodableFromHandlers(decode, encode);
}

/// Variant of [Codable] that can encode and decode a generic value of type [T] with one type parameter [A].
abstract class Codable1<T, A> implements Codable<T>, Decodable1<T, A>, Encodable1<T, A> {
  const Codable1();
}

final class _DecodableFromHandler<T> implements Decodable<T> {
  const _DecodableFromHandler(this._decode);

  final T Function(Decoder decoder) _decode;

  @override
  T decode(Decoder decoder) => _decode(decoder);
}

final class _EncodableFromHandler<T> implements Encodable<T> {
  const _EncodableFromHandler(this._encode);

  final void Function(T value, Encoder encoder) _encode;

  @override
  void encode(T value, Encoder encoder) => _encode(value, encoder);
}

final class _CodableFromHandlers<T> implements Codable<T> {
  const _CodableFromHandlers(this._decode, this._encode);

  final T Function(Decoder decoder) _decode;
  final void Function(T value, Encoder encoder) _encode;

  @override
  T decode(Decoder decoder) => _decode(decoder);
  @override
  void encode(T value, Encoder encoder) => _encode(value, encoder);
}

abstract interface class Encodable<T> {
  /// Encodes the [value] using the [encoder].
  ///
  /// The implementation must use one of the typed [Encoder]s `.encode...()` methods to encode the value.
  /// It is expected to call exactly one of the encoding methods a single time. Never more or less.
  void encode(T value, Encoder encoder);

  /// Creates an [Encodable] from a handler function.
  factory Encodable.fromHandler(void Function(T value, Encoder encoder) encode) => _EncodableFromHandler(encode);
}

abstract interface class Encoder {
}

final class _SelfEncodableFromHandler implements SelfEncodable {
  const _SelfEncodableFromHandler(this._encode);

  final void Function(Encoder encoder) _encode;

  @override
  void encode(Encoder encoder) => _encode(encoder);
}

abstract interface class SelfEncodable {
  /// Encodes itself using the [encoder].
  ///
  /// The implementation should use one of the typed [Encoder]s `.encode...()` methods to encode the value.
  /// It is expected to call exactly one of the encoding methods a single time. Never more or less.
  void encode(Encoder encoder);

  /// Creates a [SelfEncodable] from a handler function.
  factory SelfEncodable.fromHandler(void Function(Encoder encoder) encode) => _SelfEncodableFromHandler(encode);
}

Here is the json details of the error report copied from the VSCode PROBLEM pane. The line numbers will match the standalone_error.dart file included directly above:
VS Code Dart Code:

Identifier
dart-code.dart-code
Version
3.105.20250203
Last Updated
2025-02-03, 09:49:43
[{
	"resource": "/C:/src/codable_workspace/packages/codable/test/augment_test/generics/basic/model/standalone_error.dart",
	"owner": "_generated_diagnostic_collection_name_#0",
	"code": {
		"value": "argument_type_not_assignable",
		"target": {
			"$mid": 1,
			"path": "/diagnostics/argument_type_not_assignable",
			"scheme": "https",
			"authority": "dart.dev"
		}
	},
	"severity": 8,
	"message": "The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'. ",
	"source": "dart",
	"startLineNumber": 49,
	"startColumn": 55,
	"endLineNumber": 49,
	"endColumn": 65,
	"relatedInformation": [
		{
			"startLineNumber": 152,
			"startColumn": 26,
			"endLineNumber": 152,
			"endColumn": 35,
			"message": "Encodable is defined in C:\\src\\codable_workspace\\packages\\codable\\test\\augment_test\\generics\\basic\\model\\standalone_error.dart",
			"resource": "/C:/src/codable_workspace/packages/codable/test/augment_test/generics/basic/model/standalone_error.dart"
		},
		{
			"startLineNumber": 152,
			"startColumn": 26,
			"endLineNumber": 152,
			"endColumn": 35,
			"message": "Encodable is defined in C:\\src\\codable_workspace\\packages\\codable\\test\\augment_test\\generics\\basic\\model\\standalone_error.dart",
			"resource": "/C:/src/codable_workspace/packages/codable/test/augment_test/generics/basic/model/standalone_error.dart"
		}
	]
},{
	"resource": "/C:/src/codable_workspace/packages/codable/test/augment_test/generics/basic/model/standalone_error.dart",
	"owner": "_generated_diagnostic_collection_name_#0",
	"code": {
		"value": "argument_type_not_assignable",
		"target": {
			"$mid": 1,
			"path": "/diagnostics/argument_type_not_assignable",
			"scheme": "https",
			"authority": "dart.dev"
		}
	},
	"severity": 8,
	"message": "The argument type 'Encodable<T>?' can't be assigned to the parameter type 'Encodable<T>?'. ",
	"source": "dart",
	"startLineNumber": 58,
	"startColumn": 27,
	"endLineNumber": 58,
	"endColumn": 37,
	"relatedInformation": [
		{
			"startLineNumber": 152,
			"startColumn": 26,
			"endLineNumber": 152,
			"endColumn": 35,
			"message": "Encodable is defined in C:\\src\\codable_workspace\\packages\\codable\\test\\augment_test\\generics\\basic\\model\\standalone_error.dart",
			"resource": "/C:/src/codable_workspace/packages/codable/test/augment_test/generics/basic/model/standalone_error.dart"
		},
		{
			"startLineNumber": 152,
			"startColumn": 26,
			"endLineNumber": 152,
			"endColumn": 35,
			"message": "Encodable is defined in C:\\src\\codable_workspace\\packages\\codable\\test\\augment_test\\generics\\basic\\model\\standalone_error.dart",
			"resource": "/C:/src/codable_workspace/packages/codable/test/augment_test/generics/basic/model/standalone_error.dart"
		}
	]
},{
	"resource": "/C:/src/codable_workspace/packages/codable/test/augment_test/generics/basic/model/standalone_error.dart",
	"owner": "cSpell",
	"severity": 2,
	"message": "\"dartargument\": Unknown word.",
	"source": "cSpell",
	"startLineNumber": 4,
	"startColumn": 92,
	"endLineNumber": 4,
	"endColumn": 104
}]

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2A bug or feature request we're likely to work onarea-dart-modelFor issues related to conformance to the language spec in the parser, compilers or the CLI analyzer.feature-augmentationsImplementation of the augmentations featuretype-bugIncorrect behavior (everything from a crash to more subtle misbehavior)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions