001 /*
002 * Copyright 2010-2013 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.jet.lang.resolve.calls;
018
019 import com.intellij.lang.ASTNode;
020 import com.intellij.psi.PsiElement;
021 import com.intellij.psi.util.PsiTreeUtil;
022 import org.jetbrains.annotations.NotNull;
023 import org.jetbrains.annotations.Nullable;
024 import org.jetbrains.jet.lang.descriptors.*;
025 import org.jetbrains.jet.lang.evaluate.ConstantExpressionEvaluator;
026 import org.jetbrains.jet.lang.psi.*;
027 import org.jetbrains.jet.lang.resolve.BindingContext;
028 import org.jetbrains.jet.lang.resolve.BindingTrace;
029 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
030 import org.jetbrains.jet.lang.resolve.TemporaryBindingTrace;
031 import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext;
032 import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode;
033 import org.jetbrains.jet.lang.resolve.calls.context.ResolutionContext;
034 import org.jetbrains.jet.lang.resolve.calls.context.TemporaryTraceAndCache;
035 import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
036 import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCallWithTrace;
037 import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults;
038 import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResultsImpl;
039 import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResultsUtil;
040 import org.jetbrains.jet.lang.resolve.calls.util.CallMaker;
041 import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
042 import org.jetbrains.jet.lang.resolve.constants.IntegerValueConstant;
043 import org.jetbrains.jet.lang.resolve.name.Name;
044 import org.jetbrains.jet.lang.resolve.scopes.ChainedScope;
045 import org.jetbrains.jet.lang.resolve.scopes.JetScope;
046 import org.jetbrains.jet.lang.resolve.scopes.JetScopeImpl;
047 import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
048 import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
049 import org.jetbrains.jet.lang.types.*;
050 import org.jetbrains.jet.lang.types.expressions.BasicExpressionTypingVisitor;
051 import org.jetbrains.jet.lang.types.expressions.DataFlowUtils;
052 import org.jetbrains.jet.lang.types.expressions.ExpressionTypingContext;
053 import org.jetbrains.jet.lang.types.expressions.ExpressionTypingServices;
054 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
055 import org.jetbrains.jet.lexer.JetTokens;
056 import org.jetbrains.jet.utils.Printer;
057
058 import javax.inject.Inject;
059 import java.util.ArrayList;
060 import java.util.Collections;
061 import java.util.List;
062
063 import static org.jetbrains.jet.lang.diagnostics.Errors.*;
064 import static org.jetbrains.jet.lang.psi.JetPsiUtil.isLHSOfDot;
065 import static org.jetbrains.jet.lang.resolve.BindingContext.*;
066 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getStaticNestedClassesScope;
067 import static org.jetbrains.jet.lang.resolve.calls.context.ContextDependency.INDEPENDENT;
068 import static org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue.NO_RECEIVER;
069 import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE;
070
071 public class CallExpressionResolver {
072 @NotNull
073 private ExpressionTypingServices expressionTypingServices;
074
075 @Inject
076 public void setExpressionTypingServices(@NotNull ExpressionTypingServices expressionTypingServices) {
077 this.expressionTypingServices = expressionTypingServices;
078 }
079
080 @Nullable
081 private JetType lookupPackageOrClassObject(@NotNull JetSimpleNameExpression expression, @NotNull ExpressionTypingContext context) {
082 Name referencedName = expression.getReferencedNameAsName();
083 final ClassifierDescriptor classifier = context.scope.getClassifier(referencedName);
084 if (classifier != null) {
085 JetType classObjectType = classifier.getClassObjectType();
086 if (classObjectType != null) {
087 context.trace.record(REFERENCE_TARGET, expression, classifier);
088 JetType result = getExtendedClassObjectType(expression, classObjectType, classifier, context);
089 checkClassObjectVisibility(classifier, expression, context);
090 return DataFlowUtils.checkType(result, expression, context);
091 }
092 }
093 JetType[] result = new JetType[1];
094 TemporaryBindingTrace temporaryTrace = TemporaryBindingTrace.create(
095 context.trace, "trace for package/class object lookup of name", referencedName);
096 if (furtherNameLookup(expression, result, context.replaceBindingTrace(temporaryTrace))) {
097 temporaryTrace.commit();
098 return DataFlowUtils.checkType(result[0], expression, context);
099 }
100 // To report NO_CLASS_OBJECT when no package found
101 if (classifier != null) {
102 if (classifier instanceof TypeParameterDescriptor) {
103 if (isLHSOfDot(expression)) {
104 context.trace.report(TYPE_PARAMETER_ON_LHS_OF_DOT.on(expression, (TypeParameterDescriptor) classifier));
105 }
106 else {
107 context.trace.report(TYPE_PARAMETER_IS_NOT_AN_EXPRESSION.on(expression, (TypeParameterDescriptor) classifier));
108 }
109 }
110 else if (!isLHSOfDot(expression)) {
111 context.trace.report(NO_CLASS_OBJECT.on(expression, classifier));
112 }
113 context.trace.record(REFERENCE_TARGET, expression, classifier);
114 JetScope scopeForStaticMembersResolution =
115 classifier instanceof ClassDescriptor
116 ? getStaticNestedClassesScope((ClassDescriptor) classifier)
117 : new JetScopeImpl() {
118 @NotNull
119 @Override
120 public DeclarationDescriptor getContainingDeclaration() {
121 return classifier;
122 }
123
124 @Override
125 public String toString() {
126 return "Scope for the type parameter on the left hand side of dot";
127 }
128
129 @Override
130 public void printScopeStructure(@NotNull Printer p) {
131 p.println(toString(), " for ", classifier);
132 }
133 };
134 return new PackageType(referencedName, scopeForStaticMembersResolution, NO_RECEIVER);
135 }
136 temporaryTrace.commit();
137 return result[0];
138 }
139
140 private static void checkClassObjectVisibility(
141 @NotNull ClassifierDescriptor classifier,
142 @NotNull JetSimpleNameExpression expression,
143 @NotNull ExpressionTypingContext context
144 ) {
145 if (!(classifier instanceof ClassDescriptor)) return;
146 ClassDescriptor classObject = ((ClassDescriptor) classifier).getClassObjectDescriptor();
147 assert classObject != null : "This check should be done only for classes with class objects: " + classifier;
148 DeclarationDescriptor from = context.containingDeclaration;
149 if (!Visibilities.isVisible(classObject, from)) {
150 context.trace.report(INVISIBLE_MEMBER.on(expression, classObject, classObject.getVisibility(), from));
151 }
152 }
153
154 @NotNull
155 private static JetType getExtendedClassObjectType(
156 @NotNull JetSimpleNameExpression expression,
157 @NotNull JetType classObjectType,
158 @NotNull ClassifierDescriptor classifier,
159 @NotNull ResolutionContext context
160 ) {
161 if (!isLHSOfDot(expression) || !(classifier instanceof ClassDescriptor)) {
162 return classObjectType;
163 }
164 ClassDescriptor classDescriptor = (ClassDescriptor) classifier;
165
166 if (classDescriptor.getKind() == ClassKind.ENUM_ENTRY) {
167 return classObjectType;
168 }
169
170 List<JetScope> scopes = new ArrayList<JetScope>(3);
171
172 scopes.add(classObjectType.getMemberScope());
173 scopes.add(getStaticNestedClassesScope(classDescriptor));
174
175 Name referencedName = expression.getReferencedNameAsName();
176 PackageViewDescriptor packageView = context.scope.getPackage(referencedName);
177 if (packageView != null) {
178 //for enums loaded from java binaries
179 scopes.add(packageView.getMemberScope());
180 }
181
182 JetScope scope = new ChainedScope(
183 classifier, "Member scope for extended class object type " + classifier, scopes.toArray(new JetScope[scopes.size()])
184 );
185 return new PackageType(referencedName, scope, new ExpressionReceiver(expression, classObjectType));
186 }
187
188 private boolean furtherNameLookup(
189 @NotNull JetSimpleNameExpression expression,
190 @NotNull JetType[] result,
191 @NotNull ResolutionContext context
192 ) {
193 PackageType packageType = lookupPackageType(expression, context);
194 if (packageType == null) {
195 return false;
196 }
197 if (isLHSOfDot(expression)) {
198 result[0] = packageType;
199 return true;
200 }
201 context.trace.report(EXPRESSION_EXPECTED_PACKAGE_FOUND.on(expression));
202 result[0] = ErrorUtils.createErrorType("Type for " + expression.getReferencedNameAsName());
203 return false;
204 }
205
206 @Nullable
207 private PackageType lookupPackageType(@NotNull JetSimpleNameExpression expression, @NotNull ResolutionContext context) {
208 Name name = expression.getReferencedNameAsName();
209 PackageViewDescriptor packageView = context.scope.getPackage(name);
210 if (packageView == null) {
211 return null;
212 }
213 context.trace.record(REFERENCE_TARGET, expression, packageView);
214
215 // Construct a PackageType with everything from the package and with nested classes of the corresponding class (if any)
216 JetScope scope;
217 ClassifierDescriptor classifier = context.scope.getClassifier(name);
218 if (classifier instanceof ClassDescriptor) {
219 scope = new ChainedScope(
220 packageView, "Package type member scope for " + expression.getText(), packageView.getMemberScope(), getStaticNestedClassesScope((ClassDescriptor) classifier)
221 );
222 }
223 else {
224 scope = packageView.getMemberScope();
225 }
226 return new PackageType(name, scope, NO_RECEIVER);
227 }
228
229 @Nullable
230 public ResolvedCallWithTrace<FunctionDescriptor> getResolvedCallForFunction(
231 @NotNull Call call, @NotNull JetExpression callExpression,
232 @NotNull ResolutionContext context, @NotNull CheckValueArgumentsMode checkArguments,
233 @NotNull boolean[] result
234 ) {
235 CallResolver callResolver = expressionTypingServices.getCallResolver();
236 OverloadResolutionResultsImpl<FunctionDescriptor> results = callResolver.resolveFunctionCall(
237 BasicCallResolutionContext.create(context, call, checkArguments));
238 if (!results.isNothing()) {
239 checkSuper(call.getExplicitReceiver(), results, context.trace, callExpression);
240 result[0] = true;
241 return OverloadResolutionResultsUtil.getResultingCall(results, context.contextDependency);
242 }
243 result[0] = false;
244 return null;
245 }
246
247 @Nullable
248 private JetType getVariableType(@NotNull JetSimpleNameExpression nameExpression, @NotNull ReceiverValue receiver,
249 @Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context, @NotNull boolean[] result
250 ) {
251 TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create(
252 context, "trace to resolve as local variable or property", nameExpression);
253 CallResolver callResolver = expressionTypingServices.getCallResolver();
254 Call call = CallMaker.makePropertyCall(receiver, callOperationNode, nameExpression);
255 BasicCallResolutionContext contextForVariable = BasicCallResolutionContext.create(
256 context.replaceTraceAndCache(temporaryForVariable),
257 call, CheckValueArgumentsMode.ENABLED);
258 OverloadResolutionResults<VariableDescriptor> resolutionResult = callResolver.resolveSimpleProperty(contextForVariable);
259 if (resolutionResult.isSuccess()) {
260 temporaryForVariable.commit();
261 checkSuper(receiver, resolutionResult, context.trace, nameExpression);
262 result[0] = true;
263 return resolutionResult.isSingleResult() ? resolutionResult.getResultingDescriptor().getReturnType() : null;
264 }
265
266 ExpressionTypingContext newContext = receiver.exists()
267 ? context.replaceScope(receiver.getType().getMemberScope())
268 : context;
269 TemporaryTraceAndCache temporaryForPackageOrClassObject = TemporaryTraceAndCache.create(
270 context, "trace to resolve as package or class object", nameExpression);
271 JetType jetType = lookupPackageOrClassObject(nameExpression, newContext.replaceTraceAndCache(temporaryForPackageOrClassObject));
272 if (jetType != null) {
273 temporaryForPackageOrClassObject.commit();
274
275 // Uncommitted changes in temp context
276 context.trace.record(RESOLUTION_SCOPE, nameExpression, context.scope);
277 if (context.dataFlowInfo.hasTypeInfoConstraints()) {
278 context.trace.record(NON_DEFAULT_EXPRESSION_DATA_FLOW, nameExpression, context.dataFlowInfo);
279 }
280 result[0] = true;
281 return jetType;
282 }
283 temporaryForVariable.commit();
284 result[0] = !resolutionResult.isNothing();
285 return resolutionResult.isSingleResult() ? resolutionResult.getResultingDescriptor().getReturnType() : null;
286 }
287
288 @NotNull
289 public JetTypeInfo getSimpleNameExpressionTypeInfo(@NotNull JetSimpleNameExpression nameExpression, @NotNull ReceiverValue receiver,
290 @Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context
291 ) {
292 boolean[] result = new boolean[1];
293
294 TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create(
295 context, "trace to resolve as variable", nameExpression);
296 JetType type = getVariableType(nameExpression, receiver, callOperationNode, context.replaceTraceAndCache(temporaryForVariable), result);
297 if (result[0]) {
298 temporaryForVariable.commit();
299 if (type instanceof PackageType && !isLHSOfDot(nameExpression)) {
300 type = null;
301 }
302 return JetTypeInfo.create(type, context.dataFlowInfo);
303 }
304
305 Call call = CallMaker.makeCall(nameExpression, receiver, callOperationNode, nameExpression, Collections.<ValueArgument>emptyList());
306 TemporaryTraceAndCache temporaryForFunction = TemporaryTraceAndCache.create(
307 context, "trace to resolve as function", nameExpression);
308 ResolutionContext newContext = context.replaceTraceAndCache(temporaryForFunction);
309 ResolvedCall<FunctionDescriptor> resolvedCall = getResolvedCallForFunction(
310 call, nameExpression, newContext, CheckValueArgumentsMode.ENABLED, result);
311 if (result[0]) {
312 FunctionDescriptor functionDescriptor = resolvedCall != null ? resolvedCall.getResultingDescriptor() : null;
313 temporaryForFunction.commit();
314 boolean hasValueParameters = functionDescriptor == null || functionDescriptor.getValueParameters().size() > 0;
315 context.trace.report(FUNCTION_CALL_EXPECTED.on(nameExpression, nameExpression, hasValueParameters));
316 type = functionDescriptor != null ? functionDescriptor.getReturnType() : null;
317 return JetTypeInfo.create(type, context.dataFlowInfo);
318 }
319
320 temporaryForVariable.commit();
321 return JetTypeInfo.create(null, context.dataFlowInfo);
322 }
323
324 @NotNull
325 public JetTypeInfo getCallExpressionTypeInfo(
326 @NotNull JetCallExpression callExpression, @NotNull ReceiverValue receiver,
327 @Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context
328 ) {
329 JetTypeInfo typeInfo = getCallExpressionTypeInfoWithoutFinalTypeCheck(callExpression, receiver, callOperationNode, context);
330 if (context.contextDependency == INDEPENDENT) {
331 DataFlowUtils.checkType(typeInfo, callExpression, context);
332 }
333 return typeInfo;
334 }
335
336 @NotNull
337 public JetTypeInfo getCallExpressionTypeInfoWithoutFinalTypeCheck(
338 @NotNull JetCallExpression callExpression, @NotNull ReceiverValue receiver,
339 @Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context
340 ) {
341 boolean[] result = new boolean[1];
342 Call call = CallMaker.makeCall(receiver, callOperationNode, callExpression);
343
344 TemporaryTraceAndCache temporaryForFunction = TemporaryTraceAndCache.create(
345 context, "trace to resolve as function call", callExpression);
346 ResolvedCallWithTrace<FunctionDescriptor> resolvedCall = getResolvedCallForFunction(
347 call, callExpression, context.replaceTraceAndCache(temporaryForFunction),
348 CheckValueArgumentsMode.ENABLED, result);
349 if (result[0]) {
350 FunctionDescriptor functionDescriptor = resolvedCall != null ? resolvedCall.getResultingDescriptor() : null;
351 temporaryForFunction.commit();
352 if (callExpression.getValueArgumentList() == null && callExpression.getFunctionLiteralArguments().isEmpty()) {
353 // there are only type arguments
354 boolean hasValueParameters = functionDescriptor == null || functionDescriptor.getValueParameters().size() > 0;
355 context.trace.report(FUNCTION_CALL_EXPECTED.on(callExpression, callExpression, hasValueParameters));
356 }
357 if (functionDescriptor == null) {
358 return JetTypeInfo.create(null, context.dataFlowInfo);
359 }
360 if (functionDescriptor instanceof ConstructorDescriptor && DescriptorUtils.isAnnotationClass(functionDescriptor.getContainingDeclaration())) {
361 if (!canInstantiateAnnotationClass(callExpression)) {
362 context.trace.report(ANNOTATION_CLASS_CONSTRUCTOR_CALL.on(callExpression));
363 }
364 }
365
366 JetType type = functionDescriptor.getReturnType();
367
368 return JetTypeInfo.create(type, resolvedCall.getDataFlowInfoForArguments().getResultInfo());
369 }
370
371 JetExpression calleeExpression = callExpression.getCalleeExpression();
372 if (calleeExpression instanceof JetSimpleNameExpression && callExpression.getTypeArgumentList() == null) {
373 TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create(
374 context, "trace to resolve as variable with 'invoke' call", callExpression);
375 JetType type = getVariableType((JetSimpleNameExpression) calleeExpression, receiver, callOperationNode,
376 context.replaceTraceAndCache(temporaryForVariable), result);
377 if (result[0]) {
378 temporaryForVariable.commit();
379 context.trace.report(FUNCTION_EXPECTED.on((JetReferenceExpression) calleeExpression, calleeExpression,
380 type != null ? type : ErrorUtils.createErrorType("")));
381 return JetTypeInfo.create(null, context.dataFlowInfo);
382 }
383 }
384 temporaryForFunction.commit();
385 return JetTypeInfo.create(null, context.dataFlowInfo);
386 }
387
388 private static boolean canInstantiateAnnotationClass(@NotNull JetCallExpression expression) {
389 PsiElement parent = expression.getParent();
390 if (parent instanceof JetValueArgument) {
391 return PsiTreeUtil.getParentOfType(parent, JetAnnotationEntry.class) != null;
392 }
393 else if (parent instanceof JetParameter) {
394 JetClass jetClass = PsiTreeUtil.getParentOfType(parent, JetClass.class);
395 if (jetClass != null) {
396 return jetClass.hasModifier(JetTokens.ANNOTATION_KEYWORD);
397 }
398 }
399 return false;
400 }
401
402 private static void checkSuper(
403 @NotNull ReceiverValue receiverValue,
404 @NotNull OverloadResolutionResults<?> results,
405 @NotNull BindingTrace trace,
406 @NotNull JetExpression expression
407 ) {
408 if (!results.isSingleResult()) return;
409 if (!(receiverValue instanceof ExpressionReceiver)) return;
410 JetExpression receiver = ((ExpressionReceiver) receiverValue).getExpression();
411 CallableDescriptor descriptor = results.getResultingDescriptor();
412 if (receiver instanceof JetSuperExpression && descriptor instanceof MemberDescriptor) {
413 if (((MemberDescriptor) descriptor).getModality() == Modality.ABSTRACT) {
414 trace.report(ABSTRACT_SUPER_CALL.on(expression));
415 }
416 }
417 }
418
419 @NotNull
420 private JetTypeInfo getSelectorReturnTypeInfo(
421 @NotNull ReceiverValue receiver,
422 @Nullable ASTNode callOperationNode,
423 @NotNull JetExpression selectorExpression,
424 @NotNull ExpressionTypingContext context
425 ) {
426 if (selectorExpression instanceof JetCallExpression) {
427 return getCallExpressionTypeInfoWithoutFinalTypeCheck((JetCallExpression) selectorExpression, receiver,
428 callOperationNode, context);
429 }
430 else if (selectorExpression instanceof JetSimpleNameExpression) {
431 return getSimpleNameExpressionTypeInfo((JetSimpleNameExpression) selectorExpression, receiver, callOperationNode, context);
432 }
433 else {
434 context.trace.report(ILLEGAL_SELECTOR.on(selectorExpression, selectorExpression.getText()));
435 }
436 return JetTypeInfo.create(null, context.dataFlowInfo);
437 }
438
439 @NotNull
440 public JetTypeInfo getQualifiedExpressionTypeInfo(
441 @NotNull JetQualifiedExpression expression, @NotNull ExpressionTypingContext context
442 ) {
443 // TODO : functions as values
444 JetExpression selectorExpression = expression.getSelectorExpression();
445 JetExpression receiverExpression = expression.getReceiverExpression();
446 ResolutionContext contextForReceiver = context.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
447 JetTypeInfo receiverTypeInfo = expressionTypingServices.getTypeInfo(receiverExpression, contextForReceiver);
448 JetType receiverType = receiverTypeInfo.getType();
449 if (selectorExpression == null) return JetTypeInfo.create(null, context.dataFlowInfo);
450 if (receiverType == null) receiverType = ErrorUtils.createErrorType("Type for " + expression.getText());
451
452 context = context.replaceDataFlowInfo(receiverTypeInfo.getDataFlowInfo());
453
454 JetTypeInfo selectorReturnTypeInfo = getSelectorReturnTypeInfo(
455 new ExpressionReceiver(receiverExpression, receiverType),
456 expression.getOperationTokenNode(), selectorExpression, context);
457 JetType selectorReturnType = selectorReturnTypeInfo.getType();
458
459 //TODO move further
460 if (!(receiverType instanceof PackageType) && expression.getOperationSign() == JetTokens.SAFE_ACCESS) {
461 if (selectorReturnType != null && !KotlinBuiltIns.getInstance().isUnit(selectorReturnType)) {
462 if (receiverType.isNullable()) {
463 selectorReturnType = TypeUtils.makeNullable(selectorReturnType);
464 }
465 }
466 }
467
468 // TODO : this is suspicious: remove this code?
469 if (selectorReturnType != null) {
470 context.trace.record(BindingContext.EXPRESSION_TYPE, selectorExpression, selectorReturnType);
471 }
472
473 CompileTimeConstant<?> value = ConstantExpressionEvaluator.object$.evaluate(expression, context.trace, context.expectedType);
474 if (value instanceof IntegerValueConstant && ((IntegerValueConstant) value).isPure()) {
475 return BasicExpressionTypingVisitor.createCompileTimeConstantTypeInfo(value, expression, context);
476 }
477
478 JetTypeInfo typeInfo = JetTypeInfo.create(selectorReturnType, selectorReturnTypeInfo.getDataFlowInfo());
479 if (context.contextDependency == INDEPENDENT) {
480 DataFlowUtils.checkType(typeInfo, expression, context);
481 }
482 return typeInfo;
483 }
484 }