001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.resolve; 018 019 import kotlin.CollectionsKt; 020 import kotlin.Unit; 021 import kotlin.jvm.functions.Function1; 022 import org.jetbrains.annotations.NotNull; 023 import org.jetbrains.annotations.Nullable; 024 import org.jetbrains.kotlin.descriptors.*; 025 import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl; 026 import org.jetbrains.kotlin.descriptors.impl.PropertyAccessorDescriptorImpl; 027 import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl; 028 import org.jetbrains.kotlin.name.Name; 029 import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; 030 import org.jetbrains.kotlin.types.KotlinType; 031 import org.jetbrains.kotlin.types.TypeConstructor; 032 import org.jetbrains.kotlin.types.checker.KotlinTypeChecker; 033 034 import java.util.*; 035 036 import static org.jetbrains.kotlin.resolve.OverridingUtil.OverrideCompatibilityInfo.Result.*; 037 038 public class OverridingUtil { 039 040 private static final List<ExternalOverridabilityCondition> EXTERNAL_CONDITIONS = 041 CollectionsKt.toList(ServiceLoader.load( 042 ExternalOverridabilityCondition.class, 043 ExternalOverridabilityCondition.class.getClassLoader() 044 )); 045 046 public static final OverridingUtil DEFAULT = new OverridingUtil(new KotlinTypeChecker.TypeConstructorEquality() { 047 @Override 048 public boolean equals(@NotNull TypeConstructor a, @NotNull TypeConstructor b) { 049 return a.equals(b); 050 } 051 }); 052 053 @NotNull 054 public static OverridingUtil createWithEqualityAxioms(@NotNull KotlinTypeChecker.TypeConstructorEquality equalityAxioms) { 055 return new OverridingUtil(equalityAxioms); 056 } 057 058 private final KotlinTypeChecker.TypeConstructorEquality equalityAxioms; 059 060 private OverridingUtil(KotlinTypeChecker.TypeConstructorEquality axioms) { 061 equalityAxioms = axioms; 062 } 063 064 @NotNull 065 public OverrideCompatibilityInfo isOverridableBy(@NotNull CallableDescriptor superDescriptor, @NotNull CallableDescriptor subDescriptor) { 066 return isOverridableBy(superDescriptor, subDescriptor, false); 067 } 068 069 @NotNull 070 public OverrideCompatibilityInfo isOverridableByIncludingReturnType(@NotNull CallableDescriptor superDescriptor, @NotNull CallableDescriptor subDescriptor) { 071 return isOverridableBy(superDescriptor, subDescriptor, true); 072 } 073 074 @NotNull 075 private OverrideCompatibilityInfo isOverridableBy( 076 @NotNull CallableDescriptor superDescriptor, 077 @NotNull CallableDescriptor subDescriptor, 078 boolean checkReturnType 079 ) { 080 if (superDescriptor instanceof FunctionDescriptor && !(subDescriptor instanceof FunctionDescriptor) || 081 superDescriptor instanceof PropertyDescriptor && !(subDescriptor instanceof PropertyDescriptor)) { 082 return OverrideCompatibilityInfo.incompatible("Member kind mismatch"); 083 } 084 085 if (!(superDescriptor instanceof FunctionDescriptor) && !(superDescriptor instanceof PropertyDescriptor)) { 086 throw new IllegalArgumentException("This type of CallableDescriptor cannot be checked for overridability: " + superDescriptor); 087 } 088 089 // TODO: check outside of this method 090 if (!superDescriptor.getName().equals(subDescriptor.getName())) { 091 return OverrideCompatibilityInfo.incompatible("Name mismatch"); 092 } 093 094 OverrideCompatibilityInfo receiverAndParameterResult = checkReceiverAndParameterCount(superDescriptor, subDescriptor); 095 if (receiverAndParameterResult != null) { 096 return receiverAndParameterResult; 097 } 098 099 List<KotlinType> superValueParameters = compiledValueParameters(superDescriptor); 100 List<KotlinType> subValueParameters = compiledValueParameters(subDescriptor); 101 102 List<TypeParameterDescriptor> superTypeParameters = superDescriptor.getTypeParameters(); 103 List<TypeParameterDescriptor> subTypeParameters = subDescriptor.getTypeParameters(); 104 105 if (superTypeParameters.size() != subTypeParameters.size()) { 106 for (int i = 0; i < superValueParameters.size(); ++i) { 107 // TODO: compare erasure 108 if (!KotlinTypeChecker.DEFAULT.equalTypes(superValueParameters.get(i), subValueParameters.get(i))) { 109 return OverrideCompatibilityInfo.incompatible("Type parameter number mismatch"); 110 } 111 } 112 return OverrideCompatibilityInfo.conflict("Type parameter number mismatch"); 113 } 114 115 KotlinTypeChecker typeChecker = createTypeChecker(superTypeParameters, subTypeParameters); 116 117 for (int i = 0; i < superTypeParameters.size(); i++) { 118 if (!areTypeParametersEquivalent(superTypeParameters.get(i), subTypeParameters.get(i), typeChecker)) { 119 return OverrideCompatibilityInfo.incompatible("Type parameter bounds mismatch"); 120 } 121 } 122 123 for (int i = 0; i < superValueParameters.size(); i++) { 124 if (!areTypesEquivalent(superValueParameters.get(i), subValueParameters.get(i), typeChecker)) { 125 return OverrideCompatibilityInfo.incompatible("Value parameter type mismatch"); 126 } 127 } 128 129 if (checkReturnType) { 130 KotlinType superReturnType = superDescriptor.getReturnType(); 131 KotlinType subReturnType = subDescriptor.getReturnType(); 132 133 if (superReturnType != null && subReturnType != null) { 134 boolean bothErrors = subReturnType.isError() && superReturnType.isError(); 135 if (!bothErrors && !typeChecker.isSubtypeOf(subReturnType, superReturnType)) { 136 return OverrideCompatibilityInfo.conflict("Return type mismatch"); 137 } 138 } 139 } 140 141 for (ExternalOverridabilityCondition externalCondition : EXTERNAL_CONDITIONS) { 142 if (!externalCondition.isOverridable(superDescriptor, subDescriptor)) { 143 return OverrideCompatibilityInfo.incompatible("External condition failed"); 144 } 145 } 146 147 return OverrideCompatibilityInfo.success(); 148 } 149 150 @NotNull 151 private KotlinTypeChecker createTypeChecker( 152 @NotNull List<TypeParameterDescriptor> firstParameters, 153 @NotNull List<TypeParameterDescriptor> secondParameters 154 ) { 155 assert firstParameters.size() == secondParameters.size() : 156 "Should be the same number of type parameters: " + firstParameters + " vs " + secondParameters; 157 if (firstParameters.isEmpty()) return KotlinTypeChecker.withAxioms(equalityAxioms); 158 159 final Map<TypeConstructor, TypeConstructor> matchingTypeConstructors = new HashMap<TypeConstructor, TypeConstructor>(); 160 for (int i = 0; i < firstParameters.size(); i++) { 161 matchingTypeConstructors.put(firstParameters.get(i).getTypeConstructor(), secondParameters.get(i).getTypeConstructor()); 162 } 163 164 return KotlinTypeChecker.withAxioms(new KotlinTypeChecker.TypeConstructorEquality() { 165 @Override 166 public boolean equals(@NotNull TypeConstructor a, @NotNull TypeConstructor b) { 167 if (equalityAxioms.equals(a, b)) return true; 168 TypeConstructor img1 = matchingTypeConstructors.get(a); 169 TypeConstructor img2 = matchingTypeConstructors.get(b); 170 return (img1 != null && img1.equals(b)) || (img2 != null && img2.equals(a)); 171 } 172 }); 173 } 174 175 @Nullable 176 static OverrideCompatibilityInfo checkReceiverAndParameterCount( 177 CallableDescriptor superDescriptor, 178 CallableDescriptor subDescriptor 179 ) { 180 if ((superDescriptor.getExtensionReceiverParameter() == null) != (subDescriptor.getExtensionReceiverParameter() == null)) { 181 return OverrideCompatibilityInfo.incompatible("Receiver presence mismatch"); 182 } 183 184 if (superDescriptor.getValueParameters().size() != subDescriptor.getValueParameters().size()) { 185 return OverrideCompatibilityInfo.incompatible("Value parameter number mismatch"); 186 } 187 188 return null; 189 } 190 191 private static boolean areTypesEquivalent( 192 @NotNull KotlinType typeInSuper, 193 @NotNull KotlinType typeInSub, 194 @NotNull KotlinTypeChecker typeChecker 195 ) { 196 boolean bothErrors = typeInSuper.isError() && typeInSub.isError(); 197 return bothErrors || typeChecker.equalTypes(typeInSuper, typeInSub); 198 } 199 200 // See JLS 8, 8.4.4 Generic Methods 201 // TODO: use TypeSubstitutor instead 202 private static boolean areTypeParametersEquivalent( 203 @NotNull TypeParameterDescriptor superTypeParameter, 204 @NotNull TypeParameterDescriptor subTypeParameter, 205 @NotNull KotlinTypeChecker typeChecker 206 ) { 207 List<KotlinType> superBounds = superTypeParameter.getUpperBounds(); 208 List<KotlinType> subBounds = new ArrayList<KotlinType>(subTypeParameter.getUpperBounds()); 209 if (superBounds.size() != subBounds.size()) return false; 210 211 outer: 212 for (KotlinType superBound : superBounds) { 213 ListIterator<KotlinType> it = subBounds.listIterator(); 214 while (it.hasNext()) { 215 KotlinType subBound = it.next(); 216 if (areTypesEquivalent(superBound, subBound, typeChecker)) { 217 it.remove(); 218 continue outer; 219 } 220 } 221 return false; 222 } 223 224 return true; 225 } 226 227 static List<KotlinType> compiledValueParameters(CallableDescriptor callableDescriptor) { 228 ReceiverParameterDescriptor receiverParameter = callableDescriptor.getExtensionReceiverParameter(); 229 List<KotlinType> parameters = new ArrayList<KotlinType>(); 230 if (receiverParameter != null) { 231 parameters.add(receiverParameter.getType()); 232 } 233 for (ValueParameterDescriptor valueParameterDescriptor : callableDescriptor.getValueParameters()) { 234 parameters.add(valueParameterDescriptor.getType()); 235 } 236 return parameters; 237 } 238 239 public static void generateOverridesInFunctionGroup( 240 @SuppressWarnings("UnusedParameters") 241 @NotNull Name name, //DO NOT DELETE THIS PARAMETER: needed to make sure all descriptors have the same name 242 @NotNull Collection<? extends CallableMemberDescriptor> membersFromSupertypes, 243 @NotNull Collection<? extends CallableMemberDescriptor> membersFromCurrent, 244 @NotNull ClassDescriptor current, 245 @NotNull DescriptorSink sink 246 ) { 247 Collection<CallableMemberDescriptor> notOverridden = new LinkedHashSet<CallableMemberDescriptor>(membersFromSupertypes); 248 249 for (CallableMemberDescriptor fromCurrent : membersFromCurrent) { 250 Collection<CallableMemberDescriptor> bound = 251 extractAndBindOverridesForMember(fromCurrent, membersFromSupertypes, current, sink); 252 notOverridden.removeAll(bound); 253 } 254 255 createAndBindFakeOverrides(current, notOverridden, sink); 256 } 257 258 private static Collection<CallableMemberDescriptor> extractAndBindOverridesForMember( 259 @NotNull CallableMemberDescriptor fromCurrent, 260 @NotNull Collection<? extends CallableMemberDescriptor> descriptorsFromSuper, 261 @NotNull ClassDescriptor current, 262 @NotNull DescriptorSink sink 263 ) { 264 Collection<CallableMemberDescriptor> bound = new ArrayList<CallableMemberDescriptor>(descriptorsFromSuper.size()); 265 for (CallableMemberDescriptor fromSupertype : descriptorsFromSuper) { 266 OverrideCompatibilityInfo.Result result = DEFAULT.isOverridableBy(fromSupertype, fromCurrent).getResult(); 267 268 boolean isVisible = Visibilities.isVisible(ReceiverValue.IRRELEVANT_RECEIVER, fromSupertype, current); 269 switch (result) { 270 case OVERRIDABLE: 271 if (isVisible) { 272 fromCurrent.addOverriddenDescriptor(fromSupertype); 273 } 274 bound.add(fromSupertype); 275 break; 276 case CONFLICT: 277 if (isVisible) { 278 sink.conflict(fromSupertype, fromCurrent); 279 } 280 bound.add(fromSupertype); 281 break; 282 case INCOMPATIBLE: 283 break; 284 } 285 } 286 return bound; 287 } 288 289 private static void createAndBindFakeOverrides( 290 @NotNull ClassDescriptor current, 291 @NotNull Collection<CallableMemberDescriptor> notOverridden, 292 @NotNull DescriptorSink sink 293 ) { 294 Queue<CallableMemberDescriptor> fromSuperQueue = new LinkedList<CallableMemberDescriptor>(notOverridden); 295 while (!fromSuperQueue.isEmpty()) { 296 CallableMemberDescriptor notOverriddenFromSuper = VisibilityUtilKt.findMemberWithMaxVisibility(fromSuperQueue); 297 Collection<CallableMemberDescriptor> overridables = 298 extractMembersOverridableInBothWays(notOverriddenFromSuper, fromSuperQueue, sink); 299 createAndBindFakeOverride(overridables, current, sink); 300 } 301 } 302 303 private static boolean isMoreSpecific(@NotNull CallableMemberDescriptor a, @NotNull CallableMemberDescriptor b) { 304 if (a instanceof SimpleFunctionDescriptor) { 305 assert b instanceof SimpleFunctionDescriptor : "b is " + b.getClass(); 306 307 KotlinType aReturnType = a.getReturnType(); 308 assert aReturnType != null; 309 KotlinType bReturnType = b.getReturnType(); 310 assert bReturnType != null; 311 312 return KotlinTypeChecker.DEFAULT.isSubtypeOf(aReturnType, bReturnType); 313 } 314 if (a instanceof PropertyDescriptor) { 315 assert b instanceof PropertyDescriptor : "b is " + b.getClass(); 316 317 if (((PropertyDescriptor) a).isVar() || ((PropertyDescriptor) b).isVar()) { 318 return ((PropertyDescriptor) a).isVar(); 319 } 320 321 // both vals 322 return KotlinTypeChecker.DEFAULT.isSubtypeOf(((PropertyDescriptor) a).getType(), ((PropertyDescriptor) b).getType()); 323 } 324 throw new IllegalArgumentException("Unexpected callable: " + a.getClass()); 325 } 326 327 private static CallableMemberDescriptor selectMostSpecificMemberFromSuper(@NotNull Collection<CallableMemberDescriptor> overridables) { 328 CallableMemberDescriptor result = null; 329 for (CallableMemberDescriptor overridable : overridables) { 330 if (result == null || isMoreSpecific(overridable, result)) { 331 result = overridable; 332 } 333 } 334 return result; 335 } 336 337 private static void createAndBindFakeOverride( 338 @NotNull Collection<CallableMemberDescriptor> overridables, 339 @NotNull ClassDescriptor current, 340 @NotNull DescriptorSink sink 341 ) { 342 Collection<CallableMemberDescriptor> visibleOverridables = filterVisibleFakeOverrides(current, overridables); 343 boolean allInvisible = visibleOverridables.isEmpty(); 344 Collection<CallableMemberDescriptor> effectiveOverridden = allInvisible ? overridables : visibleOverridables; 345 346 Modality modality = getMinimalModality(effectiveOverridden); 347 Visibility visibility = allInvisible ? Visibilities.INVISIBLE_FAKE : Visibilities.INHERITED; 348 CallableMemberDescriptor mostSpecific = selectMostSpecificMemberFromSuper(effectiveOverridden); 349 CallableMemberDescriptor fakeOverride = 350 mostSpecific.copy(current, modality, visibility, CallableMemberDescriptor.Kind.FAKE_OVERRIDE, false); 351 for (CallableMemberDescriptor descriptor : effectiveOverridden) { 352 fakeOverride.addOverriddenDescriptor(descriptor); 353 } 354 sink.addFakeOverride(fakeOverride); 355 } 356 357 @NotNull 358 private static Modality getMinimalModality(@NotNull Collection<CallableMemberDescriptor> descriptors) { 359 Modality modality = Modality.ABSTRACT; 360 for (CallableMemberDescriptor descriptor : descriptors) { 361 if (descriptor.getModality().compareTo(modality) < 0) { 362 modality = descriptor.getModality(); 363 } 364 } 365 return modality; 366 } 367 368 @NotNull 369 private static Collection<CallableMemberDescriptor> filterVisibleFakeOverrides( 370 @NotNull final ClassDescriptor current, 371 @NotNull Collection<CallableMemberDescriptor> toFilter 372 ) { 373 return CollectionsKt.filter(toFilter, new Function1<CallableMemberDescriptor, Boolean>() { 374 @Override 375 public Boolean invoke(CallableMemberDescriptor descriptor) { 376 //nested class could capture private member, so check for private visibility added 377 return !Visibilities.isPrivate(descriptor.getVisibility()) && 378 Visibilities.isVisible(ReceiverValue.IRRELEVANT_RECEIVER, descriptor, current); 379 } 380 }); 381 } 382 383 @NotNull 384 private static Collection<CallableMemberDescriptor> extractMembersOverridableInBothWays( 385 @NotNull CallableMemberDescriptor overrider, 386 @NotNull Queue<CallableMemberDescriptor> extractFrom, 387 @NotNull DescriptorSink sink 388 ) { 389 Collection<CallableMemberDescriptor> overridable = new ArrayList<CallableMemberDescriptor>(); 390 overridable.add(overrider); 391 for (Iterator<CallableMemberDescriptor> iterator = extractFrom.iterator(); iterator.hasNext(); ) { 392 CallableMemberDescriptor candidate = iterator.next(); 393 if (overrider == candidate) { 394 iterator.remove(); 395 continue; 396 } 397 398 OverrideCompatibilityInfo.Result result1 = DEFAULT.isOverridableBy(candidate, overrider).getResult(); 399 OverrideCompatibilityInfo.Result result2 = DEFAULT.isOverridableBy(overrider, candidate).getResult(); 400 if (result1 == OVERRIDABLE && result2 == OVERRIDABLE) { 401 overridable.add(candidate); 402 iterator.remove(); 403 } 404 else if (result1 == CONFLICT || result2 == CONFLICT) { 405 sink.conflict(overrider, candidate); 406 iterator.remove(); 407 } 408 } 409 return overridable; 410 } 411 412 public static void resolveUnknownVisibilityForMember( 413 @NotNull CallableMemberDescriptor memberDescriptor, 414 @Nullable Function1<CallableMemberDescriptor, Unit> cannotInferVisibility 415 ) { 416 for (CallableMemberDescriptor descriptor : memberDescriptor.getOverriddenDescriptors()) { 417 if (descriptor.getVisibility() == Visibilities.INHERITED) { 418 resolveUnknownVisibilityForMember(descriptor, cannotInferVisibility); 419 } 420 } 421 422 if (memberDescriptor.getVisibility() != Visibilities.INHERITED) { 423 return; 424 } 425 426 Visibility maxVisibility = computeVisibilityToInherit(memberDescriptor); 427 Visibility visibilityToInherit; 428 if (maxVisibility == null) { 429 if (cannotInferVisibility != null) { 430 cannotInferVisibility.invoke(memberDescriptor); 431 } 432 visibilityToInherit = Visibilities.PUBLIC; 433 } 434 else { 435 visibilityToInherit = maxVisibility; 436 } 437 438 if (memberDescriptor instanceof PropertyDescriptorImpl) { 439 ((PropertyDescriptorImpl) memberDescriptor).setVisibility(visibilityToInherit); 440 for (PropertyAccessorDescriptor accessor : ((PropertyDescriptor) memberDescriptor).getAccessors()) { 441 // If we couldn't infer visibility for property, the diagnostic is already reported, no need to report it again on accessors 442 resolveUnknownVisibilityForMember(accessor, maxVisibility == null ? null : cannotInferVisibility); 443 } 444 } 445 else if (memberDescriptor instanceof FunctionDescriptorImpl) { 446 ((FunctionDescriptorImpl) memberDescriptor).setVisibility(visibilityToInherit); 447 } 448 else { 449 assert memberDescriptor instanceof PropertyAccessorDescriptorImpl; 450 ((PropertyAccessorDescriptorImpl) memberDescriptor).setVisibility(visibilityToInherit); 451 } 452 } 453 454 @Nullable 455 private static Visibility computeVisibilityToInherit(@NotNull CallableMemberDescriptor memberDescriptor) { 456 Collection<? extends CallableMemberDescriptor> overriddenDescriptors = memberDescriptor.getOverriddenDescriptors(); 457 Visibility maxVisibility = findMaxVisibility(overriddenDescriptors); 458 if (maxVisibility == null) { 459 return null; 460 } 461 if (memberDescriptor.getKind() == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { 462 for (CallableMemberDescriptor overridden : overriddenDescriptors) { 463 // An implementation (a non-abstract overridden member) of a fake override should have the maximum possible visibility 464 if (overridden.getModality() != Modality.ABSTRACT && !overridden.getVisibility().equals(maxVisibility)) { 465 return null; 466 } 467 } 468 return maxVisibility; 469 } 470 return maxVisibility.normalize(); 471 } 472 473 @Nullable 474 public static Visibility findMaxVisibility(@NotNull Collection<? extends CallableMemberDescriptor> descriptors) { 475 if (descriptors.isEmpty()) { 476 return Visibilities.DEFAULT_VISIBILITY; 477 } 478 Visibility maxVisibility = null; 479 for (CallableMemberDescriptor descriptor : descriptors) { 480 Visibility visibility = descriptor.getVisibility(); 481 assert visibility != Visibilities.INHERITED : "Visibility should have been computed for " + descriptor; 482 if (maxVisibility == null) { 483 maxVisibility = visibility; 484 continue; 485 } 486 Integer compareResult = Visibilities.compare(visibility, maxVisibility); 487 if (compareResult == null) { 488 maxVisibility = null; 489 } 490 else if (compareResult > 0) { 491 maxVisibility = visibility; 492 } 493 } 494 if (maxVisibility == null) { 495 return null; 496 } 497 for (CallableMemberDescriptor descriptor : descriptors) { 498 Integer compareResult = Visibilities.compare(maxVisibility, descriptor.getVisibility()); 499 if (compareResult == null || compareResult < 0) { 500 return null; 501 } 502 } 503 return maxVisibility; 504 } 505 506 public interface DescriptorSink { 507 void addFakeOverride(@NotNull CallableMemberDescriptor fakeOverride); 508 509 void conflict(@NotNull CallableMemberDescriptor fromSuper, @NotNull CallableMemberDescriptor fromCurrent); 510 } 511 512 public static class OverrideCompatibilityInfo { 513 public enum Result { 514 OVERRIDABLE, 515 INCOMPATIBLE, 516 CONFLICT, 517 } 518 519 private static final OverrideCompatibilityInfo SUCCESS = new OverrideCompatibilityInfo(OVERRIDABLE, "SUCCESS"); 520 521 @NotNull 522 public static OverrideCompatibilityInfo success() { 523 return SUCCESS; 524 } 525 526 @NotNull 527 public static OverrideCompatibilityInfo incompatible(@NotNull String debugMessage) { 528 return new OverrideCompatibilityInfo(INCOMPATIBLE, debugMessage); 529 } 530 531 @NotNull 532 public static OverrideCompatibilityInfo conflict(@NotNull String debugMessage) { 533 return new OverrideCompatibilityInfo(CONFLICT, debugMessage); 534 } 535 536 private final Result overridable; 537 private final String debugMessage; 538 539 public OverrideCompatibilityInfo(@NotNull Result success, @NotNull String debugMessage) { 540 this.overridable = success; 541 this.debugMessage = debugMessage; 542 } 543 544 @NotNull 545 public Result getResult() { 546 return overridable; 547 } 548 549 @NotNull 550 public String getDebugMessage() { 551 return debugMessage; 552 } 553 } 554 }