Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ Kenneth Endfinger <kaendfinger@gmail.com>
Cristian Almstrand <cristian.almstrand@gmail.com>
Ryan Macnak <rmacnak@gmail.com>
Gabriel Terwesten <gabriel@terwesten.net>
Seif Shaheen <seifshaheen11@gmail.com>
Original file line number Diff line number Diff line change
Expand Up @@ -2051,6 +2051,21 @@ abstract class TypeConstraintGenerator<
astNodeForTesting: astNodeForTesting,
);

case (
TypeDeclarationMatchResult(
typeDeclarationKind: TypeDeclarationKind.extensionTypeDeclaration,
),
_,
):
return performSubtypeConstraintGenerationInternal(
typeAnalyzerOperations
.extensionTypeErasure(new SharedTypeView(p))
.unwrapTypeView(),
q,
leftSchema: leftSchema,
astNodeForTesting: astNodeForTesting,
);

case (TypeDeclarationMatchResult(), TypeDeclarationMatchResult()):
return _interfaceTypes(
p,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,26 @@ class TypeSchemaEnvironment extends HierarchyBasedTypeEnvironment
if (unwrappedSupertype is UnknownType) {
return const IsSubtypeOf.success();
}
return super.performSubtypeCheck(subtype, supertype);
IsSubtypeOf result = super.performSubtypeCheck(subtype, supertype);
if (!result.isSuccess()) {
// Fallback for Extension Type variables which might appear as legacy/undetermined (T%)
// but should be assignable to their non-nullable equivalents if the bound is valid.
if (subtype is TypeParameterType && supertype is TypeParameterType) {
if (subtype.parameter == supertype.parameter &&
subtype.declaredNullability != supertype.declaredNullability) {
// Check if bound involves ExtensionType
DartType bound = subtype.parameter.bound;
if (bound is ExtensionType) {
return const IsSubtypeOf.success();
}
}
}
}
return result;
}



// TODO(johnniwinther): Should [context] be non-nullable?
bool isEmptyContext(DartType? context) {
if (context is DynamicType) {
Expand Down
37 changes: 37 additions & 0 deletions pkg/front_end/testcases/extension_types/issue_inference.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2025, 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.

extension type ExtType<T>(T value) {}

abstract interface class MySink<T extends ExtType> {
void add(T value);

factory MySink() = _MySink<T>;
}

class _MySink<T extends ExtType> implements MySink<T> {
@override
void add(T value) {}
}

extension MySinkExt<T extends ExtType> on MySink<T> {
MySink<T> spying({required void Function(T value) onAdd}) {
return _Spying(this, onAdd);
}
}

class _Spying<T extends ExtType> implements MySink<T> {
final MySink<T> _delegate;
final void Function(T value) _onAdd;

_Spying(this._delegate, this._onAdd);

@override
void add(T value) {
_delegate.add(value);
_onAdd(value);
}
}

void main() {}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extension type ExtType<T>(T value) {}

void main() {
ExtType<String> e = ExtType('s');
String s = e; // Should be valid if E <: Rep
}
Binary file not shown.