[analyzer] Define constraint types specific for the Analyzer

Part of https://github.com/dart-lang/sdk/issues/54902

Change-Id: I564f355e8fd8f036ee821968b5b094f37dc52b62
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397820
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
Chloe Stefantsova
2024-11-28 06:27:59 +00:00
committed by Commit Queue
parent 0224206aaa
commit bc07511c19
3 changed files with 122 additions and 206 deletions
@@ -5,7 +5,6 @@
import 'dart:math' as math;
import 'package:_fe_analyzer_shared/src/type_inference/shared_inference_log.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart';
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
import 'package:analyzer/dart/ast/ast.dart'
show
@@ -60,15 +59,7 @@ import 'package:collection/collection.dart';
class GenericInferrer {
final TypeSystemImpl _typeSystem;
final Set<TypeParameterElement> _typeParameters = Set.identity();
final Map<
TypeParameterElement,
List<
MergedTypeConstraint<
DartType,
TypeParameterElement,
PromotableElement,
InterfaceType,
InterfaceElement>>> _constraints = {};
final Map<TypeParameterElement, List<MergedTypeConstraint>> _constraints = {};
/// The list of type parameters being inferred.
final List<TypeParameterElement> _typeFormals;
@@ -159,8 +150,7 @@ class GenericInferrer {
void constrainArgument(
DartType argumentType, DartType parameterType, String parameterName,
{InterfaceElement? genericClass, required AstNode? nodeForTesting}) {
var origin = TypeConstraintFromArgument<DartType, PromotableElement,
TypeParameterElement, InterfaceType, InterfaceElement>(
var origin = TypeConstraintFromArgument(
argumentType: SharedTypeView(argumentType),
parameterType: SharedTypeView(parameterType),
parameterName: parameterName,
@@ -199,14 +189,8 @@ class GenericInferrer {
void constrainGenericFunctionInContext(
FunctionType fnType, DartType contextType,
{required AstNode? nodeForTesting}) {
var origin = TypeConstraintFromFunctionContext<
DartType,
DartType,
DartType,
PromotableElement,
TypeParameterElement,
InterfaceType,
InterfaceElement>(functionType: fnType, contextType: contextType);
var origin = TypeConstraintFromFunctionContext(
functionType: fnType, contextType: contextType);
// Since we're trying to infer the instantiation, we want to ignore type
// formals as we check the parameters and return type.
@@ -229,14 +213,8 @@ class GenericInferrer {
/// is a subtype of the [contextType].
void constrainReturnType(DartType declaredType, DartType contextType,
{required AstNode? nodeForTesting}) {
var origin = TypeConstraintFromReturnType<
DartType,
DartType,
DartType,
PromotableElement,
TypeParameterElement,
InterfaceType,
InterfaceElement>(declaredType: declaredType, contextType: contextType);
var origin = TypeConstraintFromReturnType(
declaredType: declaredType, contextType: contextType);
inferenceLogWriter?.enterConstraintGeneration(
ConstraintGenerationSource.returnType, declaredType, contextType);
_tryMatchSubtypeOf(declaredType, contextType, origin,
@@ -267,12 +245,7 @@ class GenericInferrer {
var parameterBound =
Substitution.fromPairs(_typeFormals, inferredTypes)
.substituteType(parameterBoundRaw);
var extendsConstraint = MergedTypeConstraint<
DartType,
TypeParameterElement,
PromotableElement,
InterfaceType,
InterfaceElement>.fromExtends(
var extendsConstraint = MergedTypeConstraint.fromExtends(
typeParameterName: parameter.name,
boundType: SharedTypeView(parameterBoundRaw),
extendsType: SharedTypeView(parameterBound),
@@ -445,10 +418,7 @@ class GenericInferrer {
/// type parameter which means we choose the upper bound rather than the
/// lower bound for normally covariant type parameters.
DartType _chooseTypeFromConstraints(
Iterable<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>
constraints,
Iterable<MergedTypeConstraint> constraints,
{bool toKnownType = false,
required bool isContravariant}) {
var (:lower, :upper) =
@@ -503,12 +473,10 @@ class GenericInferrer {
// TODO(kallentu): : Clean up TypeParameterElementImpl casting once
// variance is added to the interface.
var typeParam = _typeFormals[i] as TypeParameterElementImpl;
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>? extendsClause;
MergedTypeConstraint? extendsClause;
var bound = typeParam.bound;
if (bound != null) {
extendsClause = MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>.fromExtends(
extendsClause = MergedTypeConstraint.fromExtends(
typeParameterName: typeParam.name,
boundType: SharedTypeView(bound),
extendsType: SharedTypeView(
@@ -546,10 +514,7 @@ class GenericInferrer {
}
({DartType lower, DartType upper}) _computeLowerAndUpperBoundsOfConstraints(
Iterable<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>
constraints) {
Iterable<MergedTypeConstraint> constraints) {
DartType lower = UnknownInferredType.instance;
DartType upper = UnknownInferredType.instance;
for (var constraint in constraints) {
@@ -584,30 +549,20 @@ class GenericInferrer {
return element.getDisplayString();
}
String _formatError(
TypeParameterElement typeParam,
DartType inferred,
Iterable<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>
constraints) {
String _formatError(TypeParameterElement typeParam, DartType inferred,
Iterable<MergedTypeConstraint> constraints) {
var inferredStr = inferred.getDisplayString();
var intro = "Tried to infer '$inferredStr' for '${typeParam.name}'"
" which doesn't work:";
var constraintsByOrigin = <TypeConstraintOrigin<DartType, PromotableElement,
TypeParameterElement, InterfaceType, InterfaceElement>,
List<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>>{};
var constraintsByOrigin =
<TypeConstraintOrigin, List<MergedTypeConstraint>>{};
for (var c in constraints) {
constraintsByOrigin.putIfAbsent(c.origin, () => []).add(c);
}
// Only report unique constraint origins.
Iterable<
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>> isSatisfied(bool expected) =>
Iterable<MergedTypeConstraint> isSatisfied(bool expected) =>
constraintsByOrigin.values
.where((l) =>
l.every((c) => c.isSatisfiedBy(
@@ -629,28 +584,17 @@ class GenericInferrer {
'Consider passing explicit type argument(s) to the generic.\n\n';
}
DartType _inferTypeParameterFromAll(
List<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>
constraints,
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>?
extendsClause,
DartType _inferTypeParameterFromAll(List<MergedTypeConstraint> constraints,
MergedTypeConstraint? extendsClause,
{required bool isContravariant,
required TypeParameterElement typeParameterToInfer,
required Map<
TypeParameterElement,
List<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>>
required Map<TypeParameterElement, List<MergedTypeConstraint>>
inferencePhaseConstraints}) {
if (extendsClause != null) {
var (:lower, upper: _) =
_computeLowerAndUpperBoundsOfConstraints(constraints);
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>? boundConstraint;
MergedTypeConstraint? boundConstraint;
if (inferenceUsingBoundsIsEnabled) {
if (!identical(lower, UnknownInferredType.instance)) {
boundConstraint = _mergeInConstraintsFromBound(
@@ -675,20 +619,11 @@ class GenericInferrer {
}
DartType _inferTypeParameterFromContext(
Iterable<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>
constraints,
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>?
extendsClause,
Iterable<MergedTypeConstraint> constraints,
MergedTypeConstraint? extendsClause,
{required bool isContravariant,
required TypeParameterElement typeParameterToInfer,
required Map<
TypeParameterElement,
List<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>>
required Map<TypeParameterElement, List<MergedTypeConstraint>>
inferencePhaseConstraints}) {
// Both bits of the bound information should be available at the same time.
assert(extendsClause == null || typeParameterToInfer.bound != null);
@@ -710,8 +645,7 @@ class GenericInferrer {
var (:lower, upper: _) =
_computeLowerAndUpperBoundsOfConstraints(constraints);
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>? boundConstraint;
MergedTypeConstraint? boundConstraint;
if (inferenceUsingBoundsIsEnabled) {
if (!identical(lower, UnknownInferredType.instance)) {
boundConstraint = _mergeInConstraintsFromBound(
@@ -734,17 +668,11 @@ class GenericInferrer {
return t;
}
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>
_mergeInConstraintsFromBound(
{required TypeParameterElement typeParameterToInfer,
required DartType lower,
required Map<
TypeParameterElement,
List<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>>
inferencePhaseConstraints}) {
MergedTypeConstraint _mergeInConstraintsFromBound(
{required TypeParameterElement typeParameterToInfer,
required DartType lower,
required Map<TypeParameterElement, List<MergedTypeConstraint>>
inferencePhaseConstraints}) {
// The type parameter's bound may refer to itself (or other type
// parameters), so we might have to create an additional constraint.
// Consider this example from
@@ -877,14 +805,8 @@ class GenericInferrer {
/// The return value indicates whether the match was successful. If it was
/// unsuccessful, any constraints that were accumulated during the match
/// attempt have been rewound.
bool _tryMatchSubtypeOf(
DartType t1,
DartType t2,
TypeConstraintOrigin<DartType, PromotableElement, TypeParameterElement,
InterfaceType, InterfaceElement>
origin,
{required bool covariant,
required AstNode? nodeForTesting}) {
bool _tryMatchSubtypeOf(DartType t1, DartType t2, TypeConstraintOrigin origin,
{required bool covariant, required AstNode? nodeForTesting}) {
var gatherer = TypeConstraintGatherer(
typeParameters: _typeParameters,
typeSystemOperations: _typeSystemOperations,
@@ -911,21 +833,12 @@ class GenericInferrer {
return type.getDisplayString();
}
static String _formatConstraints(
Iterable<
MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>>
constraints,
static String _formatConstraints(Iterable<MergedTypeConstraint> constraints,
TypeSystemOperations typeSystemOperations) {
List<List<String>> lineParts = Set<
TypeConstraintOrigin<
DartType,
PromotableElement,
TypeParameterElement,
InterfaceType,
InterfaceElement>>.from(constraints.map((c) => c.origin))
.map((o) => o.formatError(typeSystemOperations))
.toList();
List<List<String>> lineParts =
Set<TypeConstraintOrigin>.from(constraints.map((c) => c.origin))
.map((o) => o.formatError(typeSystemOperations))
.toList();
int prefixMax = lineParts.map((p) => p[0].length).fold(0, math.max);
@@ -8,7 +8,17 @@ import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.
TypeConstraintGenerator,
TypeConstraintGeneratorMixin,
TypeConstraintGeneratorState;
import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart'
as shared
show
GeneratedTypeConstraint,
MergedTypeConstraint,
TypeConstraintFromArgument,
TypeConstraintFromExtendsClause,
TypeConstraintFromFunctionContext,
TypeConstraintFromReturnType,
TypeConstraintOrigin,
UnknownTypeConstraintOrigin;
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
@@ -18,6 +28,56 @@ import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
/// Instance of [shared.GeneratedTypeConstraint] specific to the Analyzer.
typedef GeneratedTypeConstraint = shared
.GeneratedTypeConstraint<DartType, TypeParameterElement, PromotableElement>;
/// Instance of [shared.MergedTypeConstraint] specific to the Analyzer.
typedef MergedTypeConstraint = shared.MergedTypeConstraint<DartType,
TypeParameterElement, PromotableElement, InterfaceType, InterfaceElement>;
/// Instance of [shared.TypeConstraintFromArgument] specific to the Analyzer.
typedef TypeConstraintFromArgument = shared.TypeConstraintFromArgument<DartType,
PromotableElement, TypeParameterElement, InterfaceType, InterfaceElement>;
/// Instance of [shared.TypeConstraintFromExtendsClause] specific to the Analyzer.
typedef TypeConstraintFromExtendsClause
= shared.TypeConstraintFromExtendsClause<DartType, PromotableElement,
TypeParameterElement, InterfaceType, InterfaceElement>;
/// Instance of [shared.TypeConstraintFromFunctionContext] specific to the Analyzer.
typedef TypeConstraintFromFunctionContext
= shared.TypeConstraintFromFunctionContext<
DartType,
DartType,
DartType,
PromotableElement,
TypeParameterElement,
InterfaceType,
InterfaceElement>;
/// Instance of [shared.TypeConstraintFromReturnType] specific to the Analyzer.
typedef TypeConstraintFromReturnType = shared.TypeConstraintFromReturnType<
DartType,
DartType,
DartType,
PromotableElement,
TypeParameterElement,
InterfaceType,
InterfaceElement>;
/// Instance of [shared.TypeConstraintOrigin] specific to the Analyzer.
typedef TypeConstraintOrigin = shared.TypeConstraintOrigin<DartType,
PromotableElement, TypeParameterElement, InterfaceType, InterfaceElement>;
/// Instance of [shared.UnknownTypeConstraintOrigin] specific to the Analyzer.
typedef UnknownTypeConstraintOrigin = shared.UnknownTypeConstraintOrigin<
DartType,
PromotableElement,
TypeParameterElement,
InterfaceType,
InterfaceElement>;
/// Creates sets of [GeneratedTypeConstraint]s for type parameters, based on an
/// attempt to make one type schema a subtype of another.
class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
@@ -40,9 +100,7 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
@override
final Set<TypeParameterElement> typeParametersToConstrain = Set.identity();
final List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>> _constraints = [];
final List<GeneratedTypeConstraint> _constraints = [];
final TypeSystemOperations _typeSystemOperations;
final TypeConstraintGenerationDataForTesting? dataForTesting;
@@ -72,11 +130,8 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
void addLowerConstraintForParameter(
TypeParameterElement element, DartType lower,
{required AstNode? astNodeForTesting}) {
GeneratedTypeConstraint<DartType, TypeParameterElement, PromotableElement>
generatedTypeConstraint = GeneratedTypeConstraint<
DartType,
TypeParameterElement,
PromotableElement>.lower(element, SharedTypeSchemaView(lower));
GeneratedTypeConstraint generatedTypeConstraint =
GeneratedTypeConstraint.lower(element, SharedTypeSchemaView(lower));
_constraints.add(generatedTypeConstraint);
if (dataForTesting != null && astNodeForTesting != null) {
(dataForTesting!.generatedTypeConstraints[astNodeForTesting] ??= [])
@@ -88,11 +143,8 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
void addUpperConstraintForParameter(
TypeParameterElement element, DartType upper,
{required AstNode? astNodeForTesting}) {
GeneratedTypeConstraint<DartType, TypeParameterElement, PromotableElement>
generatedTypeConstraint = GeneratedTypeConstraint<
DartType,
TypeParameterElement,
PromotableElement>.upper(element, SharedTypeSchemaView(upper));
GeneratedTypeConstraint generatedTypeConstraint =
GeneratedTypeConstraint.upper(element, SharedTypeSchemaView(upper));
_constraints.add(generatedTypeConstraint);
if (dataForTesting != null && astNodeForTesting != null) {
(dataForTesting!.generatedTypeConstraints[astNodeForTesting] ??= [])
@@ -101,16 +153,10 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
}
/// Returns the set of type constraints that was gathered.
Map<
TypeParameterElement,
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>> computeConstraints() {
var result = <TypeParameterElement,
MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement,
InterfaceType, InterfaceElement>>{};
Map<TypeParameterElement, MergedTypeConstraint> computeConstraints() {
var result = <TypeParameterElement, MergedTypeConstraint>{};
for (var parameter in typeParametersToConstrain) {
result[parameter] = MergedTypeConstraint<DartType, TypeParameterElement,
PromotableElement, InterfaceType, InterfaceElement>(
result[parameter] = MergedTypeConstraint(
lower: SharedTypeSchemaView(UnknownInferredType.instance),
upper: SharedTypeSchemaView(UnknownInferredType.instance),
origin: const UnknownTypeConstraintOrigin(),
@@ -214,11 +260,8 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
class TypeConstraintGenerationDataForTesting {
/// Map from nodes requiring type inference to the generated type constraints
/// for the node.
final Map<
AstNode,
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>> generatedTypeConstraints = {};
final Map<AstNode, List<GeneratedTypeConstraint>> generatedTypeConstraints =
{};
/// Merges [other] into the receiver, combining the constraints.
///
@@ -228,9 +271,8 @@ class TypeConstraintGenerationDataForTesting {
/// [other].
void mergeIn(TypeConstraintGenerationDataForTesting other) {
for (AstNode node in other.generatedTypeConstraints.keys) {
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>? constraints = generatedTypeConstraints[node];
List<GeneratedTypeConstraint>? constraints =
generatedTypeConstraints[node];
if (constraints != null) {
constraints.addAll(other.generatedTypeConstraints[node]!);
} else {
@@ -6,10 +6,7 @@ import 'dart:io';
import 'package:_fe_analyzer_shared/src/testing/id.dart' show ActualData, Id;
import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/analysis/testing_data.dart';
import 'package:analyzer/src/dart/element/type_constraint_gatherer.dart';
import 'package:analyzer/src/util/ast_data_extractor.dart';
@@ -20,10 +17,7 @@ main(List<String> args) async {
Directory dataDir = Directory.fromUri(
Platform.script.resolve('../../../_fe_analyzer_shared/test/inference/'
'type_constraint_generation/data'));
return runTests<
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>>(dataDir,
return runTests<List<GeneratedTypeConstraint>>(dataDir,
args: args,
createUriForFileName: createUriForFileName,
onFailure: onFailure,
@@ -31,33 +25,20 @@ main(List<String> args) async {
[analyzerDefaultConfig]));
}
class _TypeConstraintGenerationDataComputer extends DataComputer<
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>> {
class _TypeConstraintGenerationDataComputer
extends DataComputer<List<GeneratedTypeConstraint>> {
const _TypeConstraintGenerationDataComputer();
@override
DataInterpreter<
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>> get dataValidator =>
DataInterpreter<List<GeneratedTypeConstraint>> get dataValidator =>
const _TypeConstraintGenerationDataInterpreter();
@override
bool get supportsErrors => true;
@override
void computeUnitData(
TestingData testingData,
CompilationUnit unit,
Map<
Id,
ActualData<
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>>>
actualMap) {
void computeUnitData(TestingData testingData, CompilationUnit unit,
Map<Id, ActualData<List<GeneratedTypeConstraint>>> actualMap) {
_TypeConstraintGenerationDataExtractor(
testingData.uriToTypeConstraintGenerationData[
unit.declaredElement!.source.uri]!,
@@ -67,37 +48,25 @@ class _TypeConstraintGenerationDataComputer extends DataComputer<
}
}
class _TypeConstraintGenerationDataExtractor extends AstDataExtractor<
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>> {
class _TypeConstraintGenerationDataExtractor
extends AstDataExtractor<List<GeneratedTypeConstraint>> {
final TypeConstraintGenerationDataForTesting dataForTesting;
_TypeConstraintGenerationDataExtractor(
this.dataForTesting, super.uri, super.actualMap);
@override
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>? computeNodeValue(Id id, AstNode node) {
List<GeneratedTypeConstraint>? computeNodeValue(Id id, AstNode node) {
return dataForTesting.generatedTypeConstraints[node];
}
}
class _TypeConstraintGenerationDataInterpreter
implements
DataInterpreter<
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>> {
implements DataInterpreter<List<GeneratedTypeConstraint>> {
const _TypeConstraintGenerationDataInterpreter();
@override
String getText(
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>
actualData,
String getText(List<GeneratedTypeConstraint> actualData,
[String? indentation]) {
StringBuffer sb = StringBuffer();
if (actualData.isNotEmpty) {
@@ -119,11 +88,7 @@ class _TypeConstraintGenerationDataInterpreter
@override
String? isAsExpected(
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>
actualData,
String? expectedData) {
List<GeneratedTypeConstraint> actualData, String? expectedData) {
var actualDataText = getText(actualData);
if (actualDataText == expectedData) {
return null;
@@ -133,10 +98,6 @@ class _TypeConstraintGenerationDataInterpreter
}
@override
bool isEmpty(
List<
GeneratedTypeConstraint<DartType, TypeParameterElement,
PromotableElement>>?
actualData) =>
bool isEmpty(List<GeneratedTypeConstraint>? actualData) =>
actualData == null || actualData.isEmpty;
}