Dart deep-equals (#16251)

* Add support for deep equality for complex types.

* Use version of collection used by the SDK.

* Use a const instead of final.

* Use deep equality only for arrays and maps.

* Generate pet store sources.

* Downgrade version of dependency.

* Expose `DeepCollectionEquality` instance.

* Revert last change.
This commit is contained in:
Noor Dawod
2023-08-06 07:12:45 +02:00
committed by GitHub
parent f0b100a9ad
commit 1d4a6d713f
54 changed files with 179 additions and 165 deletions

View File

@@ -5,6 +5,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
@@ -23,13 +24,16 @@ part 'auth/http_bearer_auth.dart';
{{#models}}{{#model}}part 'model/{{{classFilename}}}.dart';
{{/model}}{{/models}}
/// An [ApiClient] instance that uses the default values obtained from
/// the OpenAPI specification file.
var defaultApiClient = ApiClient();
const _delimiters = {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'};
const _dateEpochMarker = 'epoch';
const _deepEquality = DeepCollectionEquality();
final _dateFormatter = DateFormat('yyyy-MM-dd');
final _regList = RegExp(r'^List<(.*)>$');
final _regSet = RegExp(r'^Set<(.*)>$');
final _regMap = RegExp(r'^Map<String,(.*)>$');
ApiClient defaultApiClient = ApiClient();
bool _isEpochMarker(String? pattern) => pattern == _dateEpochMarker || pattern == '/$_dateEpochMarker/';

View File

@@ -15,6 +15,7 @@ publish_to: {{.}}
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
collection: '^1.17.0'
http: '>=0.13.0 <0.14.0'
intl: '^0.18.0'
meta: '^1.1.8'
@@ -22,4 +23,5 @@ dev_dependencies:
test: '>=1.16.0 <1.18.0'
{{#json_serializable}}
build_runner: '^1.10.9'
json_serializable: '^3.5.1'{{/json_serializable}}
json_serializable: '^3.5.1'
{{/json_serializable}}

View File

@@ -38,7 +38,7 @@ class {{{classname}}} {
@override
bool operator ==(Object other) => identical(this, other) || other is {{{classname}}} &&
{{#vars}}
other.{{{name}}} == {{{name}}}{{^-last}} &&{{/-last}}{{#-last}};{{/-last}}
{{#isMap}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isMap}}{{^isMap}}{{#isArray}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isArray}}{{^isArray}}other.{{{name}}} == {{{name}}}{{/isArray}}{{/isMap}}{{^-last}} &&{{/-last}}{{#-last}};{{/-last}}
{{/vars}}
@override

View File

@@ -14,6 +14,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
@@ -39,13 +40,16 @@ part 'model/tag.dart';
part 'model/user.dart';
/// An [ApiClient] instance that uses the default values obtained from
/// the OpenAPI specification file.
var defaultApiClient = ApiClient();
const _delimiters = {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'};
const _dateEpochMarker = 'epoch';
const _deepEquality = DeepCollectionEquality();
final _dateFormatter = DateFormat('yyyy-MM-dd');
final _regList = RegExp(r'^List<(.*)>$');
final _regSet = RegExp(r'^Set<(.*)>$');
final _regMap = RegExp(r'^Map<String,(.*)>$');
ApiClient defaultApiClient = ApiClient();
bool _isEpochMarker(String? pattern) => pattern == _dateEpochMarker || pattern == '/$_dateEpochMarker/';

View File

@@ -44,9 +44,9 @@ class ApiResponse {
@override
bool operator ==(Object other) => identical(this, other) || other is ApiResponse &&
other.code == code &&
other.type == type &&
other.message == message;
other.code == code &&
other.type == type &&
other.message == message;
@override
int get hashCode =>

View File

@@ -35,8 +35,8 @@ class Category {
@override
bool operator ==(Object other) => identical(this, other) || other is Category &&
other.id == id &&
other.name == name;
other.id == id &&
other.name == name;
@override
int get hashCode =>

View File

@@ -60,12 +60,12 @@ class Order {
@override
bool operator ==(Object other) => identical(this, other) || other is Order &&
other.id == id &&
other.petId == petId &&
other.quantity == quantity &&
other.shipDate == shipDate &&
other.status == status &&
other.complete == complete;
other.id == id &&
other.petId == petId &&
other.quantity == quantity &&
other.shipDate == shipDate &&
other.status == status &&
other.complete == complete;
@override
int get hashCode =>

View File

@@ -48,12 +48,12 @@ class Pet {
@override
bool operator ==(Object other) => identical(this, other) || other is Pet &&
other.id == id &&
other.category == category &&
other.name == name &&
other.photoUrls == photoUrls &&
other.tags == tags &&
other.status == status;
other.id == id &&
other.category == category &&
other.name == name &&
_deepEquality.equals(other.photoUrls, photoUrls) &&
_deepEquality.equals(other.tags, tags) &&
other.status == status;
@override
int get hashCode =>

View File

@@ -35,8 +35,8 @@ class Tag {
@override
bool operator ==(Object other) => identical(this, other) || other is Tag &&
other.id == id &&
other.name == name;
other.id == id &&
other.name == name;
@override
int get hashCode =>

View File

@@ -90,14 +90,14 @@ class User {
@override
bool operator ==(Object other) => identical(this, other) || other is User &&
other.id == id &&
other.username == username &&
other.firstName == firstName &&
other.lastName == lastName &&
other.email == email &&
other.password == password &&
other.phone == phone &&
other.userStatus == userStatus;
other.id == id &&
other.username == username &&
other.firstName == firstName &&
other.lastName == lastName &&
other.email == email &&
other.password == password &&
other.phone == phone &&
other.userStatus == userStatus;
@override
int get hashCode =>

View File

@@ -9,9 +9,9 @@ homepage: 'homepage'
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
collection: '^1.17.0'
http: '>=0.13.0 <0.14.0'
intl: '^0.18.0'
meta: '^1.1.8'
dev_dependencies:
test: '>=1.16.0 <1.18.0'

View File

@@ -14,6 +14,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
@@ -84,13 +85,16 @@ part 'model/tag.dart';
part 'model/user.dart';
/// An [ApiClient] instance that uses the default values obtained from
/// the OpenAPI specification file.
var defaultApiClient = ApiClient();
const _delimiters = {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'};
const _dateEpochMarker = 'epoch';
const _deepEquality = DeepCollectionEquality();
final _dateFormatter = DateFormat('yyyy-MM-dd');
final _regList = RegExp(r'^List<(.*)>$');
final _regSet = RegExp(r'^Set<(.*)>$');
final _regMap = RegExp(r'^Map<String,(.*)>$');
ApiClient defaultApiClient = ApiClient();
bool _isEpochMarker(String? pattern) => pattern == _dateEpochMarker || pattern == '/$_dateEpochMarker/';

View File

@@ -23,8 +23,8 @@ class AdditionalPropertiesClass {
@override
bool operator ==(Object other) => identical(this, other) || other is AdditionalPropertiesClass &&
other.mapProperty == mapProperty &&
other.mapOfMapProperty == mapOfMapProperty;
_deepEquality.equals(other.mapProperty, mapProperty) &&
_deepEquality.equals(other.mapOfMapProperty, mapOfMapProperty);
@override
int get hashCode =>

View File

@@ -35,8 +35,8 @@ class AllOfWithSingleRef {
@override
bool operator ==(Object other) => identical(this, other) || other is AllOfWithSingleRef &&
other.username == username &&
other.singleRefType == singleRefType;
other.username == username &&
other.singleRefType == singleRefType;
@override
int get hashCode =>

View File

@@ -23,8 +23,8 @@ class Animal {
@override
bool operator ==(Object other) => identical(this, other) || other is Animal &&
other.className == className &&
other.color == color;
other.className == className &&
other.color == color;
@override
int get hashCode =>

View File

@@ -44,9 +44,9 @@ class ApiResponse {
@override
bool operator ==(Object other) => identical(this, other) || other is ApiResponse &&
other.code == code &&
other.type == type &&
other.message == message;
other.code == code &&
other.type == type &&
other.message == message;
@override
int get hashCode =>

View File

@@ -20,7 +20,7 @@ class ArrayOfArrayOfNumberOnly {
@override
bool operator ==(Object other) => identical(this, other) || other is ArrayOfArrayOfNumberOnly &&
other.arrayArrayNumber == arrayArrayNumber;
_deepEquality.equals(other.arrayArrayNumber, arrayArrayNumber);
@override
int get hashCode =>

View File

@@ -20,7 +20,7 @@ class ArrayOfNumberOnly {
@override
bool operator ==(Object other) => identical(this, other) || other is ArrayOfNumberOnly &&
other.arrayNumber == arrayNumber;
_deepEquality.equals(other.arrayNumber, arrayNumber);
@override
int get hashCode =>

View File

@@ -26,9 +26,9 @@ class ArrayTest {
@override
bool operator ==(Object other) => identical(this, other) || other is ArrayTest &&
other.arrayOfString == arrayOfString &&
other.arrayArrayOfInteger == arrayArrayOfInteger &&
other.arrayArrayOfModel == arrayArrayOfModel;
_deepEquality.equals(other.arrayOfString, arrayOfString) &&
_deepEquality.equals(other.arrayArrayOfInteger, arrayArrayOfInteger) &&
_deepEquality.equals(other.arrayArrayOfModel, arrayArrayOfModel);
@override
int get hashCode =>

View File

@@ -72,12 +72,12 @@ class Capitalization {
@override
bool operator ==(Object other) => identical(this, other) || other is Capitalization &&
other.smallCamel == smallCamel &&
other.capitalCamel == capitalCamel &&
other.smallSnake == smallSnake &&
other.capitalSnake == capitalSnake &&
other.sCAETHFlowPoints == sCAETHFlowPoints &&
other.ATT_NAME == ATT_NAME;
other.smallCamel == smallCamel &&
other.capitalCamel == capitalCamel &&
other.smallSnake == smallSnake &&
other.capitalSnake == capitalSnake &&
other.sCAETHFlowPoints == sCAETHFlowPoints &&
other.ATT_NAME == ATT_NAME;
@override
int get hashCode =>

View File

@@ -32,9 +32,9 @@ class Cat {
@override
bool operator ==(Object other) => identical(this, other) || other is Cat &&
other.className == className &&
other.color == color &&
other.declawed == declawed;
other.className == className &&
other.color == color &&
other.declawed == declawed;
@override
int get hashCode =>

View File

@@ -29,8 +29,8 @@ class Category {
@override
bool operator ==(Object other) => identical(this, other) || other is Category &&
other.id == id &&
other.name == name;
other.id == id &&
other.name == name;
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class ClassModel {
@override
bool operator ==(Object other) => identical(this, other) || other is ClassModel &&
other.class_ == class_;
other.class_ == class_;
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class DeprecatedObject {
@override
bool operator ==(Object other) => identical(this, other) || other is DeprecatedObject &&
other.name == name;
other.name == name;
@override
int get hashCode =>

View File

@@ -32,9 +32,9 @@ class Dog {
@override
bool operator ==(Object other) => identical(this, other) || other is Dog &&
other.className == className &&
other.color == color &&
other.breed == breed;
other.className == className &&
other.color == color &&
other.breed == breed;
@override
int get hashCode =>

View File

@@ -23,8 +23,8 @@ class EnumArrays {
@override
bool operator ==(Object other) => identical(this, other) || other is EnumArrays &&
other.justSymbol == justSymbol &&
other.arrayEnum == arrayEnum;
other.justSymbol == justSymbol &&
_deepEquality.equals(other.arrayEnum, arrayEnum);
@override
int get hashCode =>

View File

@@ -59,14 +59,14 @@ class EnumTest {
@override
bool operator ==(Object other) => identical(this, other) || other is EnumTest &&
other.enumString == enumString &&
other.enumStringRequired == enumStringRequired &&
other.enumInteger == enumInteger &&
other.enumNumber == enumNumber &&
other.outerEnum == outerEnum &&
other.outerEnumInteger == outerEnumInteger &&
other.outerEnumDefaultValue == outerEnumDefaultValue &&
other.outerEnumIntegerDefaultValue == outerEnumIntegerDefaultValue;
other.enumString == enumString &&
other.enumStringRequired == enumStringRequired &&
other.enumInteger == enumInteger &&
other.enumNumber == enumNumber &&
other.outerEnum == outerEnum &&
other.outerEnumInteger == outerEnumInteger &&
other.outerEnumDefaultValue == outerEnumDefaultValue &&
other.outerEnumIntegerDefaultValue == outerEnumIntegerDefaultValue;
@override
int get hashCode =>

View File

@@ -29,8 +29,8 @@ class FakeBigDecimalMap200Response {
@override
bool operator ==(Object other) => identical(this, other) || other is FakeBigDecimalMap200Response &&
other.someId == someId &&
other.someMap == someMap;
other.someId == someId &&
_deepEquality.equals(other.someMap, someMap);
@override
int get hashCode =>

View File

@@ -29,8 +29,8 @@ class FileSchemaTestClass {
@override
bool operator ==(Object other) => identical(this, other) || other is FileSchemaTestClass &&
other.file == file &&
other.files == files;
other.file == file &&
_deepEquality.equals(other.files, files);
@override
int get hashCode =>

View File

@@ -20,7 +20,7 @@ class Foo {
@override
bool operator ==(Object other) => identical(this, other) || other is Foo &&
other.bar == bar;
other.bar == bar;
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class FooGetDefaultResponse {
@override
bool operator ==(Object other) => identical(this, other) || other is FooGetDefaultResponse &&
other.string == string;
other.string == string;
@override
int get hashCode =>

View File

@@ -149,22 +149,22 @@ class FormatTest {
@override
bool operator ==(Object other) => identical(this, other) || other is FormatTest &&
other.integer == integer &&
other.int32 == int32 &&
other.int64 == int64 &&
other.number == number &&
other.float == float &&
other.double_ == double_ &&
other.decimal == decimal &&
other.string == string &&
other.byte == byte &&
other.binary == binary &&
other.date == date &&
other.dateTime == dateTime &&
other.uuid == uuid &&
other.password == password &&
other.patternWithDigits == patternWithDigits &&
other.patternWithDigitsAndDelimiter == patternWithDigitsAndDelimiter;
other.integer == integer &&
other.int32 == int32 &&
other.int64 == int64 &&
other.number == number &&
other.float == float &&
other.double_ == double_ &&
other.decimal == decimal &&
other.string == string &&
other.byte == byte &&
other.binary == binary &&
other.date == date &&
other.dateTime == dateTime &&
other.uuid == uuid &&
other.password == password &&
other.patternWithDigits == patternWithDigits &&
other.patternWithDigitsAndDelimiter == patternWithDigitsAndDelimiter;
@override
int get hashCode =>

View File

@@ -35,8 +35,8 @@ class HasOnlyReadOnly {
@override
bool operator ==(Object other) => identical(this, other) || other is HasOnlyReadOnly &&
other.bar == bar &&
other.foo == foo;
other.bar == bar &&
other.foo == foo;
@override
int get hashCode =>

View File

@@ -20,7 +20,7 @@ class HealthCheckResult {
@override
bool operator ==(Object other) => identical(this, other) || other is HealthCheckResult &&
other.nullableMessage == nullableMessage;
other.nullableMessage == nullableMessage;
@override
int get hashCode =>

View File

@@ -29,10 +29,10 @@ class MapTest {
@override
bool operator ==(Object other) => identical(this, other) || other is MapTest &&
other.mapMapOfString == mapMapOfString &&
other.mapOfEnumString == mapOfEnumString &&
other.directMap == directMap &&
other.indirectMap == indirectMap;
_deepEquality.equals(other.mapMapOfString, mapMapOfString) &&
_deepEquality.equals(other.mapOfEnumString, mapOfEnumString) &&
_deepEquality.equals(other.directMap, directMap) &&
_deepEquality.equals(other.indirectMap, indirectMap);
@override
int get hashCode =>

View File

@@ -38,9 +38,9 @@ class MixedPropertiesAndAdditionalPropertiesClass {
@override
bool operator ==(Object other) => identical(this, other) || other is MixedPropertiesAndAdditionalPropertiesClass &&
other.uuid == uuid &&
other.dateTime == dateTime &&
other.map == map;
other.uuid == uuid &&
other.dateTime == dateTime &&
_deepEquality.equals(other.map, map);
@override
int get hashCode =>

View File

@@ -35,8 +35,8 @@ class Model200Response {
@override
bool operator ==(Object other) => identical(this, other) || other is Model200Response &&
other.name == name &&
other.class_ == class_;
other.name == name &&
other.class_ == class_;
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class ModelClient {
@override
bool operator ==(Object other) => identical(this, other) || other is ModelClient &&
other.client == client;
other.client == client;
@override
int get hashCode =>

View File

@@ -27,7 +27,7 @@ class ModelFile {
@override
bool operator ==(Object other) => identical(this, other) || other is ModelFile &&
other.sourceURI == sourceURI;
other.sourceURI == sourceURI;
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class ModelList {
@override
bool operator ==(Object other) => identical(this, other) || other is ModelList &&
other.n123list == n123list;
other.n123list == n123list;
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class ModelReturn {
@override
bool operator ==(Object other) => identical(this, other) || other is ModelReturn &&
other.return_ == return_;
other.return_ == return_;
@override
int get hashCode =>

View File

@@ -47,10 +47,10 @@ class Name {
@override
bool operator ==(Object other) => identical(this, other) || other is Name &&
other.name == name &&
other.snakeCase == snakeCase &&
other.property == property &&
other.n123number == n123number;
other.name == name &&
other.snakeCase == snakeCase &&
other.property == property &&
other.n123number == n123number;
@override
int get hashCode =>

View File

@@ -53,18 +53,18 @@ class NullableClass {
@override
bool operator ==(Object other) => identical(this, other) || other is NullableClass &&
other.integerProp == integerProp &&
other.numberProp == numberProp &&
other.booleanProp == booleanProp &&
other.stringProp == stringProp &&
other.dateProp == dateProp &&
other.datetimeProp == datetimeProp &&
other.arrayNullableProp == arrayNullableProp &&
other.arrayAndItemsNullableProp == arrayAndItemsNullableProp &&
other.arrayItemsNullable == arrayItemsNullable &&
other.objectNullableProp == objectNullableProp &&
other.objectAndItemsNullableProp == objectAndItemsNullableProp &&
other.objectItemsNullable == objectItemsNullable;
other.integerProp == integerProp &&
other.numberProp == numberProp &&
other.booleanProp == booleanProp &&
other.stringProp == stringProp &&
other.dateProp == dateProp &&
other.datetimeProp == datetimeProp &&
_deepEquality.equals(other.arrayNullableProp, arrayNullableProp) &&
_deepEquality.equals(other.arrayAndItemsNullableProp, arrayAndItemsNullableProp) &&
_deepEquality.equals(other.arrayItemsNullable, arrayItemsNullable) &&
_deepEquality.equals(other.objectNullableProp, objectNullableProp) &&
_deepEquality.equals(other.objectAndItemsNullableProp, objectAndItemsNullableProp) &&
_deepEquality.equals(other.objectItemsNullable, objectItemsNullable);
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class NumberOnly {
@override
bool operator ==(Object other) => identical(this, other) || other is NumberOnly &&
other.justNumber == justNumber;
other.justNumber == justNumber;
@override
int get hashCode =>

View File

@@ -47,10 +47,10 @@ class ObjectWithDeprecatedFields {
@override
bool operator ==(Object other) => identical(this, other) || other is ObjectWithDeprecatedFields &&
other.uuid == uuid &&
other.id == id &&
other.deprecatedRef == deprecatedRef &&
other.bars == bars;
other.uuid == uuid &&
other.id == id &&
other.deprecatedRef == deprecatedRef &&
_deepEquality.equals(other.bars, bars);
@override
int get hashCode =>

View File

@@ -60,12 +60,12 @@ class Order {
@override
bool operator ==(Object other) => identical(this, other) || other is Order &&
other.id == id &&
other.petId == petId &&
other.quantity == quantity &&
other.shipDate == shipDate &&
other.status == status &&
other.complete == complete;
other.id == id &&
other.petId == petId &&
other.quantity == quantity &&
other.shipDate == shipDate &&
other.status == status &&
other.complete == complete;
@override
int get hashCode =>

View File

@@ -44,9 +44,9 @@ class OuterComposite {
@override
bool operator ==(Object other) => identical(this, other) || other is OuterComposite &&
other.myNumber == myNumber &&
other.myString == myString &&
other.myBoolean == myBoolean;
other.myNumber == myNumber &&
other.myString == myString &&
other.myBoolean == myBoolean;
@override
int get hashCode =>

View File

@@ -20,7 +20,7 @@ class OuterObjectWithEnumProperty {
@override
bool operator ==(Object other) => identical(this, other) || other is OuterObjectWithEnumProperty &&
other.value == value;
other.value == value;
@override
int get hashCode =>

View File

@@ -48,12 +48,12 @@ class Pet {
@override
bool operator ==(Object other) => identical(this, other) || other is Pet &&
other.id == id &&
other.category == category &&
other.name == name &&
other.photoUrls == photoUrls &&
other.tags == tags &&
other.status == status;
other.id == id &&
other.category == category &&
other.name == name &&
_deepEquality.equals(other.photoUrls, photoUrls) &&
_deepEquality.equals(other.tags, tags) &&
other.status == status;
@override
int get hashCode =>

View File

@@ -35,8 +35,8 @@ class ReadOnlyFirst {
@override
bool operator ==(Object other) => identical(this, other) || other is ReadOnlyFirst &&
other.bar == bar &&
other.baz == baz;
other.bar == bar &&
other.baz == baz;
@override
int get hashCode =>

View File

@@ -26,7 +26,7 @@ class SpecialModelName {
@override
bool operator ==(Object other) => identical(this, other) || other is SpecialModelName &&
other.dollarSpecialLeftSquareBracketPropertyPeriodNameRightSquareBracket == dollarSpecialLeftSquareBracketPropertyPeriodNameRightSquareBracket;
other.dollarSpecialLeftSquareBracketPropertyPeriodNameRightSquareBracket == dollarSpecialLeftSquareBracketPropertyPeriodNameRightSquareBracket;
@override
int get hashCode =>

View File

@@ -35,8 +35,8 @@ class Tag {
@override
bool operator ==(Object other) => identical(this, other) || other is Tag &&
other.id == id &&
other.name == name;
other.id == id &&
other.name == name;
@override
int get hashCode =>

View File

@@ -90,14 +90,14 @@ class User {
@override
bool operator ==(Object other) => identical(this, other) || other is User &&
other.id == id &&
other.username == username &&
other.firstName == firstName &&
other.lastName == lastName &&
other.email == email &&
other.password == password &&
other.phone == phone &&
other.userStatus == userStatus;
other.id == id &&
other.username == username &&
other.firstName == firstName &&
other.lastName == lastName &&
other.email == email &&
other.password == password &&
other.phone == phone &&
other.userStatus == userStatus;
@override
int get hashCode =>

View File

@@ -9,9 +9,9 @@ homepage: 'homepage'
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
collection: '^1.17.0'
http: '>=0.13.0 <0.14.0'
intl: '^0.18.0'
meta: '^1.1.8'
dev_dependencies:
test: '>=1.16.0 <1.18.0'