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 com.google.common.collect.Lists;
020 import com.intellij.psi.PsiElement;
021 import kotlin.Pair;
022 import org.jetbrains.annotations.NotNull;
023 import org.jetbrains.annotations.Nullable;
024 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
025 import org.jetbrains.kotlin.descriptors.*;
026 import org.jetbrains.kotlin.diagnostics.rendering.Renderers;
027 import org.jetbrains.kotlin.name.Name;
028 import org.jetbrains.kotlin.psi.*;
029 import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
030 import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystem;
031 import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemCompleter;
032 import org.jetbrains.kotlin.resolve.calls.inference.TypeVariableKt;
033 import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
034 import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults;
035 import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
036 import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory;
037 import org.jetbrains.kotlin.resolve.scopes.ScopeUtils;
038 import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
039 import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
040 import org.jetbrains.kotlin.resolve.validation.OperatorValidator;
041 import org.jetbrains.kotlin.resolve.validation.SymbolUsageValidator;
042 import org.jetbrains.kotlin.types.*;
043 import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
044 import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext;
045 import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices;
046 import org.jetbrains.kotlin.types.expressions.FakeCallResolver;
047 import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
048
049 import java.util.Collections;
050 import java.util.List;
051
052 import static org.jetbrains.kotlin.diagnostics.Errors.*;
053 import static org.jetbrains.kotlin.psi.KtPsiFactoryKt.KtPsiFactory;
054 import static org.jetbrains.kotlin.resolve.BindingContext.*;
055 import static org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.FROM_COMPLETER;
056 import static org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE;
057 import static org.jetbrains.kotlin.types.TypeUtils.noExpectedType;
058 import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.createFakeExpressionOfType;
059
060 public class DelegatedPropertyResolver {
061
062 public static final Name PROPERTY_DELEGATED_FUNCTION_NAME = Name.identifier("propertyDelegated");
063 public static final Name GETTER_NAME = Name.identifier("getValue");
064 public static final Name SETTER_NAME = Name.identifier("setValue");
065
066 public static final Name OLD_GETTER_NAME = Name.identifier("get");
067 public static final Name OLD_SETTER_NAME = Name.identifier("set");
068
069 @NotNull private final ExpressionTypingServices expressionTypingServices;
070 @NotNull private final FakeCallResolver fakeCallResolver;
071 @NotNull private final KotlinBuiltIns builtIns;
072 @NotNull private final SymbolUsageValidator symbolUsageValidator;
073
074 public DelegatedPropertyResolver(
075 @NotNull SymbolUsageValidator symbolUsageValidator,
076 @NotNull KotlinBuiltIns builtIns,
077 @NotNull FakeCallResolver fakeCallResolver,
078 @NotNull ExpressionTypingServices expressionTypingServices
079 ) {
080 this.symbolUsageValidator = symbolUsageValidator;
081 this.builtIns = builtIns;
082 this.fakeCallResolver = fakeCallResolver;
083 this.expressionTypingServices = expressionTypingServices;
084 }
085
086 @Nullable
087 public KotlinType getDelegatedPropertyGetMethodReturnType(
088 @NotNull PropertyDescriptor propertyDescriptor,
089 @NotNull KtExpression delegateExpression,
090 @NotNull KotlinType delegateType,
091 @NotNull BindingTrace trace,
092 @NotNull LexicalScope delegateFunctionsScope
093 ) {
094 resolveDelegatedPropertyConventionMethod(propertyDescriptor, delegateExpression, delegateType, trace, delegateFunctionsScope, true);
095 ResolvedCall<FunctionDescriptor> resolvedCall =
096 trace.getBindingContext().get(DELEGATED_PROPERTY_RESOLVED_CALL, propertyDescriptor.getGetter());
097 return resolvedCall != null ? resolvedCall.getResultingDescriptor().getReturnType() : null;
098 }
099
100 public void resolveDelegatedPropertyGetMethod(
101 @NotNull PropertyDescriptor propertyDescriptor,
102 @NotNull KtExpression delegateExpression,
103 @NotNull KotlinType delegateType,
104 @NotNull BindingTrace trace,
105 @NotNull LexicalScope delegateFunctionsScope
106 ) {
107 KotlinType returnType = getDelegatedPropertyGetMethodReturnType(
108 propertyDescriptor, delegateExpression, delegateType, trace, delegateFunctionsScope);
109 KotlinType propertyType = propertyDescriptor.getType();
110
111 /* Do not check return type of get() method of delegate for properties with DeferredType because property type is taken from it */
112 if (!(propertyType instanceof DeferredType) && returnType != null && !KotlinTypeChecker.DEFAULT.isSubtypeOf(returnType, propertyType)) {
113 Call call = trace.getBindingContext().get(DELEGATED_PROPERTY_CALL, propertyDescriptor.getGetter());
114 assert call != null : "Call should exists for " + propertyDescriptor.getGetter();
115 trace.report(DELEGATE_SPECIAL_FUNCTION_RETURN_TYPE_MISMATCH
116 .on(delegateExpression, renderCall(call, trace.getBindingContext()), propertyDescriptor.getType(), returnType));
117 }
118 }
119
120 public void resolveDelegatedPropertySetMethod(
121 @NotNull PropertyDescriptor propertyDescriptor,
122 @NotNull KtExpression delegateExpression,
123 @NotNull KotlinType delegateType,
124 @NotNull BindingTrace trace,
125 @NotNull LexicalScope delegateFunctionsScope
126 ) {
127 resolveDelegatedPropertyConventionMethod(propertyDescriptor, delegateExpression, delegateType, trace, delegateFunctionsScope, false);
128 }
129
130 @NotNull
131 private static KtExpression createExpressionForProperty(@NotNull KtPsiFactory psiFactory) {
132 return psiFactory.createExpression("null as " + KotlinBuiltIns.FQ_NAMES.kProperty.asSingleFqName().asString() + "<*>");
133 }
134
135 public void resolveDelegatedPropertyPDMethod(
136 @NotNull PropertyDescriptor propertyDescriptor,
137 @NotNull KtExpression delegateExpression,
138 @NotNull KotlinType delegateType,
139 @NotNull BindingTrace trace,
140 @NotNull LexicalScope delegateFunctionsScope
141 ) {
142 TemporaryBindingTrace traceToResolvePDMethod = TemporaryBindingTrace.create(trace, "Trace to resolve propertyDelegated method in delegated property");
143 ExpressionTypingContext context = ExpressionTypingContext.newContext(
144 traceToResolvePDMethod, delegateFunctionsScope,
145 DataFlowInfoFactory.EMPTY, TypeUtils.NO_EXPECTED_TYPE);
146
147 KtPsiFactory psiFactory = KtPsiFactory(delegateExpression);
148 List<KtExpression> arguments = Collections.singletonList(createExpressionForProperty(psiFactory));
149 ExpressionReceiver receiver = ExpressionReceiver.Companion.create(delegateExpression, delegateType, trace.getBindingContext());
150
151 Pair<Call, OverloadResolutionResults<FunctionDescriptor>> resolutionResult =
152 fakeCallResolver.makeAndResolveFakeCallInContext(receiver, context, arguments, PROPERTY_DELEGATED_FUNCTION_NAME, delegateExpression);
153
154 Call call = resolutionResult.getFirst();
155 OverloadResolutionResults<FunctionDescriptor> functionResults = resolutionResult.getSecond();
156
157 if (!functionResults.isSuccess()) {
158 String expectedFunction = renderCall(call, traceToResolvePDMethod.getBindingContext());
159 if (functionResults.isIncomplete() || functionResults.isSingleResult() ||
160 functionResults.getResultCode() == OverloadResolutionResults.Code.MANY_FAILED_CANDIDATES) {
161 trace.report(DELEGATE_PD_METHOD_NONE_APPLICABLE.on(delegateExpression, expectedFunction, functionResults.getResultingCalls()));
162 } else if (functionResults.isAmbiguity()) {
163 trace.report(DELEGATE_SPECIAL_FUNCTION_AMBIGUITY
164 .on(delegateExpression, expectedFunction, functionResults.getResultingCalls()));
165 }
166 return;
167 }
168
169 trace.record(DELEGATED_PROPERTY_PD_RESOLVED_CALL, propertyDescriptor, functionResults.getResultingCall());
170 }
171
172 /* Resolve getValue() or setValue() methods from delegate */
173 private void resolveDelegatedPropertyConventionMethod(
174 @NotNull PropertyDescriptor propertyDescriptor,
175 @NotNull KtExpression delegateExpression,
176 @NotNull KotlinType delegateType,
177 @NotNull BindingTrace trace,
178 @NotNull LexicalScope delegateFunctionsScope,
179 boolean isGet
180 ) {
181 PropertyAccessorDescriptor accessor = isGet ? propertyDescriptor.getGetter() : propertyDescriptor.getSetter();
182 assert accessor != null : "Delegated property should have getter/setter " + propertyDescriptor + " " + delegateExpression.getText();
183
184 if (trace.getBindingContext().get(DELEGATED_PROPERTY_CALL, accessor) != null) return;
185
186 OverloadResolutionResults<FunctionDescriptor> functionResults = getDelegatedPropertyConventionMethod(
187 propertyDescriptor, delegateExpression, delegateType, trace, delegateFunctionsScope, isGet, true);
188 Call call = trace.getBindingContext().get(DELEGATED_PROPERTY_CALL, accessor);
189 assert call != null : "'getDelegatedPropertyConventionMethod' didn't record a call";
190
191 if (!functionResults.isSuccess()) {
192 String expectedFunction = renderCall(call, trace.getBindingContext());
193 if (functionResults.isSingleResult() || functionResults.isIncomplete() ||
194 functionResults.getResultCode() == OverloadResolutionResults.Code.MANY_FAILED_CANDIDATES) {
195 trace.report(DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE
196 .on(delegateExpression, expectedFunction, functionResults.getResultingCalls()));
197 }
198 else if (functionResults.isAmbiguity()) {
199 trace.report(DELEGATE_SPECIAL_FUNCTION_AMBIGUITY
200 .on(delegateExpression, expectedFunction, functionResults.getResultingCalls()));
201 }
202 else {
203 trace.report(DELEGATE_SPECIAL_FUNCTION_MISSING.on(delegateExpression, expectedFunction, delegateType));
204 }
205 return;
206 }
207
208 FunctionDescriptor resultingDescriptor = functionResults.getResultingDescriptor();
209
210 ResolvedCall<FunctionDescriptor> resultingCall = functionResults.getResultingCall();
211 PsiElement declaration = DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor);
212 if (declaration instanceof KtProperty) {
213 KtProperty property = (KtProperty) declaration;
214 KtPropertyDelegate delegate = property.getDelegate();
215 if (delegate != null) {
216 PsiElement byKeyword = delegate.getByKeywordNode().getPsi();
217
218 if (!resultingDescriptor.isOperator()) {
219 OperatorValidator.Companion.report(byKeyword, resultingDescriptor, trace);
220 }
221
222 symbolUsageValidator.validateCall(resultingCall, resultingCall.getResultingDescriptor(), trace, byKeyword);
223 }
224 }
225 trace.record(DELEGATED_PROPERTY_RESOLVED_CALL, accessor, resultingCall);
226 }
227
228 /* Resolve getValue() or setValue() methods from delegate */
229 public OverloadResolutionResults<FunctionDescriptor> getDelegatedPropertyConventionMethod(
230 @NotNull PropertyDescriptor propertyDescriptor,
231 @NotNull KtExpression delegateExpression,
232 @NotNull KotlinType delegateType,
233 @NotNull BindingTrace trace,
234 @NotNull LexicalScope delegateFunctionsScope,
235 boolean isGet,
236 boolean isComplete
237 ) {
238 PropertyAccessorDescriptor accessor = isGet ? propertyDescriptor.getGetter() : propertyDescriptor.getSetter();
239 assert accessor != null : "Delegated property should have getter/setter " + propertyDescriptor + " " + delegateExpression.getText();
240
241 KotlinType expectedType = isComplete && isGet && !(propertyDescriptor.getType() instanceof DeferredType)
242 ? propertyDescriptor.getType() : TypeUtils.NO_EXPECTED_TYPE;
243
244 ExpressionTypingContext context = ExpressionTypingContext.newContext(
245 trace, delegateFunctionsScope,
246 DataFlowInfoFactory.EMPTY, expectedType);
247
248 boolean hasThis = propertyDescriptor.getExtensionReceiverParameter() != null || propertyDescriptor.getDispatchReceiverParameter() != null;
249
250 List<KtExpression> arguments = Lists.newArrayList();
251 KtPsiFactory psiFactory = KtPsiFactory(delegateExpression);
252 arguments.add(psiFactory.createExpression(hasThis ? "this" : "null"));
253 arguments.add(createExpressionForProperty(psiFactory));
254
255 if (!isGet) {
256 KtReferenceExpression fakeArgument = (KtReferenceExpression) createFakeExpressionOfType(delegateExpression.getProject(), trace,
257 "fakeArgument" + arguments.size(),
258 propertyDescriptor.getType());
259 arguments.add(fakeArgument);
260 List<ValueParameterDescriptor> valueParameters = accessor.getValueParameters();
261 trace.record(REFERENCE_TARGET, fakeArgument, valueParameters.get(0));
262 }
263
264 Name functionName = isGet ? GETTER_NAME : SETTER_NAME;
265 ExpressionReceiver receiver = ExpressionReceiver.Companion.create(delegateExpression, delegateType, trace.getBindingContext());
266
267 Pair<Call, OverloadResolutionResults<FunctionDescriptor>> resolutionResult =
268 fakeCallResolver.makeAndResolveFakeCallInContext(receiver, context, arguments, functionName, delegateExpression);
269
270 OverloadResolutionResults<FunctionDescriptor> resolutionResults = resolutionResult.getSecond();
271
272 // Resolve get/set is getValue/setValue was not found. Temporary, for code migration
273 if (!resolutionResults.isSuccess() && !resolutionResults.isAmbiguity()) {
274 Name oldFunctionName = isGet ? OLD_GETTER_NAME : OLD_SETTER_NAME;
275 Pair<Call, OverloadResolutionResults<FunctionDescriptor>> additionalResolutionResult =
276 fakeCallResolver.makeAndResolveFakeCallInContext(receiver, context, arguments, oldFunctionName, delegateExpression);
277 if (additionalResolutionResult.getSecond().isSuccess()) {
278 FunctionDescriptor resultingDescriptor = additionalResolutionResult.getSecond().getResultingDescriptor();
279
280 PsiElement declaration = DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor);
281 if (declaration instanceof KtProperty) {
282 KtProperty property = (KtProperty) declaration;
283 KtPropertyDelegate delegate = property.getDelegate();
284 if (delegate != null) {
285 PsiElement byKeyword = delegate.getByKeywordNode().getPsi();
286
287 trace.report(DELEGATE_RESOLVED_TO_DEPRECATED_CONVENTION.on(
288 byKeyword, resultingDescriptor, delegateType, functionName.asString()));
289 }
290 }
291
292 trace.record(BindingContext.DELEGATED_PROPERTY_CALL, accessor, additionalResolutionResult.getFirst());
293 return additionalResolutionResult.getSecond();
294 }
295 }
296
297 trace.record(BindingContext.DELEGATED_PROPERTY_CALL, accessor, resolutionResult.getFirst());
298 return resolutionResults;
299 }
300
301 private static String renderCall(@NotNull Call call, @NotNull BindingContext context) {
302 KtExpression calleeExpression = call.getCalleeExpression();
303 assert calleeExpression != null : "CalleeExpression should exists for fake call of convention method";
304 StringBuilder builder = new StringBuilder(calleeExpression.getText());
305 builder.append("(");
306 List<KotlinType> argumentTypes = Lists.newArrayList();
307 for (ValueArgument argument : call.getValueArguments()) {
308 argumentTypes.add(context.getType(argument.getArgumentExpression()));
309
310 }
311 builder.append(Renderers.RENDER_COLLECTION_OF_TYPES.render(argumentTypes));
312 builder.append(")");
313 return builder.toString();
314 }
315
316 @NotNull
317 public KotlinType resolveDelegateExpression(
318 @NotNull KtExpression delegateExpression,
319 @NotNull KtProperty property,
320 @NotNull PropertyDescriptor propertyDescriptor,
321 @NotNull LexicalScope scopeForDelegate,
322 @NotNull BindingTrace trace,
323 @NotNull DataFlowInfo dataFlowInfo
324 ) {
325 TemporaryBindingTrace traceToResolveDelegatedProperty = TemporaryBindingTrace.create(trace, "Trace to resolve delegated property");
326 KtExpression calleeExpression = CallUtilKt.getCalleeExpressionIfAny(delegateExpression);
327 ConstraintSystemCompleter completer = createConstraintSystemCompleter(
328 property, propertyDescriptor, delegateExpression, scopeForDelegate, trace);
329 if (calleeExpression != null) {
330 traceToResolveDelegatedProperty.record(CONSTRAINT_SYSTEM_COMPLETER, calleeExpression, completer);
331 }
332 KotlinType delegateType = expressionTypingServices.safeGetType(scopeForDelegate, delegateExpression, NO_EXPECTED_TYPE,
333 dataFlowInfo, traceToResolveDelegatedProperty);
334 traceToResolveDelegatedProperty.commit(new TraceEntryFilter() {
335 @Override
336 public boolean accept(@Nullable WritableSlice<?, ?> slice, Object key) {
337 return slice != CONSTRAINT_SYSTEM_COMPLETER;
338 }
339 }, true);
340 return delegateType;
341 }
342
343 @NotNull
344 private ConstraintSystemCompleter createConstraintSystemCompleter(
345 @NotNull KtProperty property,
346 @NotNull final PropertyDescriptor propertyDescriptor,
347 @NotNull final KtExpression delegateExpression,
348 @NotNull LexicalScope scopeForDelegate,
349 @NotNull final BindingTrace trace
350 ) {
351 final LexicalScope delegateFunctionsScope = ScopeUtils.makeScopeForDelegateConventionFunctions(scopeForDelegate, propertyDescriptor);
352 final KotlinType expectedType = property.getTypeReference() != null ? propertyDescriptor.getType() : NO_EXPECTED_TYPE;
353 return new ConstraintSystemCompleter() {
354 @Override
355 public void completeConstraintSystem(
356 @NotNull ConstraintSystem.Builder constraintSystem, @NotNull ResolvedCall<?> resolvedCall
357 ) {
358 KotlinType returnType = resolvedCall.getCandidateDescriptor().getReturnType();
359 if (returnType == null) return;
360
361 TypeSubstitutor typeVariableSubstitutor =
362 constraintSystem.getTypeVariableSubstitutors().get(TypeVariableKt.toHandle(resolvedCall.getCall()));
363 assert typeVariableSubstitutor != null : "No substitutor in the system for call: " + resolvedCall.getCall();
364
365 TemporaryBindingTrace traceToResolveConventionMethods =
366 TemporaryBindingTrace.create(trace, "Trace to resolve delegated property convention methods");
367 OverloadResolutionResults<FunctionDescriptor>
368 getMethodResults = getDelegatedPropertyConventionMethod(
369 propertyDescriptor, delegateExpression, returnType, traceToResolveConventionMethods, delegateFunctionsScope,
370 true, false
371 );
372
373 if (conventionMethodFound(getMethodResults)) {
374 FunctionDescriptor descriptor = getMethodResults.getResultingDescriptor();
375 KotlinType returnTypeOfGetMethod = descriptor.getReturnType();
376 if (returnTypeOfGetMethod != null && !TypeUtils.noExpectedType(expectedType)) {
377 KotlinType returnTypeInSystem = typeVariableSubstitutor.substitute(returnTypeOfGetMethod, Variance.INVARIANT);
378 if (returnTypeInSystem != null) {
379 constraintSystem.addSubtypeConstraint(returnTypeInSystem, expectedType, FROM_COMPLETER.position());
380 }
381 }
382 addConstraintForThisValue(constraintSystem, typeVariableSubstitutor, descriptor);
383 }
384 if (!propertyDescriptor.isVar()) return;
385
386 // For the case: 'val v by d' (no declared type).
387 // When we add a constraint for 'set' method for delegated expression 'd' we use a type of the declared variable 'v'.
388 // But if the type isn't known yet, the constraint shouldn't be added (we try to infer the type of 'v' here as well).
389 if (propertyDescriptor.getReturnType() instanceof DeferredType) return;
390
391 OverloadResolutionResults<FunctionDescriptor>
392 setMethodResults = getDelegatedPropertyConventionMethod(
393 propertyDescriptor, delegateExpression, returnType, traceToResolveConventionMethods, delegateFunctionsScope,
394 false, false
395 );
396
397 if (conventionMethodFound(setMethodResults)) {
398 FunctionDescriptor descriptor = setMethodResults.getResultingDescriptor();
399 List<ValueParameterDescriptor> valueParameters = descriptor.getValueParameters();
400 if (valueParameters.size() == 3) {
401 ValueParameterDescriptor valueParameterForThis = valueParameters.get(2);
402
403 if (!noExpectedType(expectedType)) {
404 constraintSystem.addSubtypeConstraint(
405 expectedType,
406 typeVariableSubstitutor.substitute(valueParameterForThis.getType(), Variance.INVARIANT),
407 FROM_COMPLETER.position()
408 );
409 }
410 addConstraintForThisValue(constraintSystem, typeVariableSubstitutor, descriptor);
411 }
412 }
413 }
414
415 private boolean conventionMethodFound(@NotNull OverloadResolutionResults<FunctionDescriptor> results) {
416 return results.isSuccess() ||
417 (results.isSingleResult() &&
418 results.getResultCode() == OverloadResolutionResults.Code.SINGLE_CANDIDATE_ARGUMENT_MISMATCH);
419 }
420
421 private void addConstraintForThisValue(
422 ConstraintSystem.Builder constraintSystem,
423 TypeSubstitutor typeVariableSubstitutor,
424 FunctionDescriptor resultingDescriptor
425 ) {
426 ReceiverParameterDescriptor extensionReceiver = propertyDescriptor.getExtensionReceiverParameter();
427 ReceiverParameterDescriptor dispatchReceiver = propertyDescriptor.getDispatchReceiverParameter();
428 KotlinType typeOfThis =
429 extensionReceiver != null ? extensionReceiver.getType() :
430 dispatchReceiver != null ? dispatchReceiver.getType() :
431 builtIns.getNullableNothingType();
432
433 List<ValueParameterDescriptor> valueParameters = resultingDescriptor.getValueParameters();
434 if (valueParameters.isEmpty()) return;
435 ValueParameterDescriptor valueParameterForThis = valueParameters.get(0);
436
437 constraintSystem.addSubtypeConstraint(
438 typeOfThis,
439 typeVariableSubstitutor.substitute(valueParameterForThis.getType(), Variance.INVARIANT),
440 FROM_COMPLETER.position()
441 );
442 }
443 };
444 }
445 }