/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.initializer;
import com.intellij.psi.
PsiElement;
import org.jetbrains.annotations.
NotNull;
import org.jetbrains.kotlin.builtins.
KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.impl.
TypeAliasConstructorDescriptor;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.backend.ast.metadata.
MetadataProperties;
import org.jetbrains.kotlin.js.translate.callTranslator.
CallTranslator;
import org.jetbrains.kotlin.js.translate.context.
Namer;
import org.jetbrains.kotlin.js.translate.context.
TranslationContext;
import org.jetbrains.kotlin.js.translate.context.
UsageTracker;
import org.jetbrains.kotlin.js.translate.declaration.
DelegationTranslator;
import org.jetbrains.kotlin.js.translate.general.
AbstractTranslator;
import org.jetbrains.kotlin.js.translate.general.
Translation;
import org.jetbrains.kotlin.js.translate.reference.
CallArgumentTranslator;
import org.jetbrains.kotlin.js.translate.reference.
ReferenceTranslator;
import org.jetbrains.kotlin.js.translate.utils.
BindingUtils;
import org.jetbrains.kotlin.js.translate.utils.
JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.
JsDescriptorUtils;
import org.jetbrains.kotlin.js.translate.utils.
TranslationUtils;
import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.
AstUtilsKt;
import org.jetbrains.kotlin.lexer.
KtTokens;
import org.jetbrains.kotlin.name.
Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.
PsiUtilsKt;
import org.jetbrains.kotlin.resolve.
DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.callUtil.
CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.
DefaultValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.
ExpressionValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.
ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.
ResolvedValueArgument;
import org.jetbrains.kotlin.resolve.descriptorUtil.
DescriptorUtilsKt;
import java.util.
ArrayList;
import java.util.
Arrays;
import java.util.
Collections;
import java.util.
List;
import static org.jetbrains.kotlin.js.translate.utils.
BindingUtils.*;
import static org.jetbrains.kotlin.js.translate.utils.
FunctionBodyTranslator.setDefaultValueForArguments;
import static org.jetbrains.kotlin.js.translate.utils.
JsAstUtils.pureFqn;
import static org.jetbrains.kotlin.js.translate.utils.
PsiUtils.getPrimaryConstructorParameters;
public final class
ClassInitializerTranslator extends
AbstractTranslator {
@
NotNull
private final
KtPureClassOrObject classDeclaration;
@
NotNull
private final
JsFunction initFunction;
@
NotNull
private final
TranslationContext context;
@
NotNull
private final
ClassDescriptor classDescriptor;
private final
ConstructorDescriptor primaryConstructor;
private int
ordinal;
public
ClassInitializerTranslator(
@
NotNull KtPureClassOrObject classDeclaration,
@
NotNull TranslationContext context,
@
NotNull JsFunction initFunction
) {
super(
context);
this.
classDeclaration =
classDeclaration;
this.
initFunction =
initFunction;
this.
context =
context.
contextWithScope(
initFunction);
classDescriptor =
BindingUtils.
getClassDescriptor(
bindingContext(),
classDeclaration);
primaryConstructor =
classDescriptor.
getUnsubstitutedPrimaryConstructor();
}
public void
setOrdinal(int
ordinal) {
this.
ordinal =
ordinal;
}
@
NotNull
@
Override
protected
TranslationContext context() {
return
context;
}
public void
generateInitializeMethod(
DelegationTranslator delegationTranslator) {
addOuterClassReference(
classDescriptor);
if (
primaryConstructor != null) {
initFunction.
getBody().
getStatements().
addAll(
setDefaultValueForArguments(
primaryConstructor,
context()));
mayBeAddCallToSuperMethod(
initFunction);
//NOTE: while we translate constructor parameters we also add property initializer statements
// for properties declared as constructor parameters
initFunction.
getParameters().
addAll(
translatePrimaryConstructorParameters());
// Initialize enum 'name' and 'ordinal' before translating property initializers.
if (
classDescriptor.
getKind() ==
ClassKind.
ENUM_CLASS &&
classDeclaration instanceof
PsiElement) {
addEnumClassParameters(
initFunction, (
PsiElement)
classDeclaration);
}
}
addThrowableCall();
delegationTranslator.
addInitCode(
initFunction.
getBody().
getStatements());
new
InitializerVisitor().
traverseContainer(
classDeclaration,
context().
innerBlock(
initFunction.
getBody()));
}
private static void
addEnumClassParameters(
JsFunction constructorFunction,
PsiElement psiElement) {
JsName nameParamName =
constructorFunction.
getScope().
declareFreshName("name");
JsName ordinalParamName =
constructorFunction.
getScope().
declareFreshName("ordinal");
constructorFunction.
getParameters().
addAll(0,
Arrays.
asList(new
JsParameter(
nameParamName), new
JsParameter(
ordinalParamName)));
JsStatement nameAssignment =
JsAstUtils.
assignmentToThisField(
Namer.
ENUM_NAME_FIELD,
nameParamName.
makeRef().
source(
psiElement));
constructorFunction.
getBody().
getStatements().
add(
nameAssignment);
JsStatement ordinalAssignment =
JsAstUtils.
assignmentToThisField(
Namer.
ENUM_ORDINAL_FIELD,
ordinalParamName.
makeRef().
source(
psiElement));
constructorFunction.
getBody().
getStatements().
add(
ordinalAssignment);
}
private void
addOuterClassReference(
ClassDescriptor classDescriptor) {
JsName outerName =
context.
getOuterClassReference(
classDescriptor);
if (
outerName == null) return;
initFunction.
getParameters().
add(0, new
JsParameter(
outerName));
JsExpression paramRef =
pureFqn(
outerName, null);
JsExpression assignment =
JsAstUtils.
assignment(
pureFqn(
outerName, new
JsThisRef()),
paramRef).
source(
classDeclaration);
initFunction.
getBody().
getStatements().
add(new
JsExpressionStatement(
assignment));
}
@
NotNull
public static
JsExpression generateEnumEntryInstanceCreation(
@
NotNull TranslationContext context,
@
NotNull KtEnumEntry enumEntry,
int
ordinal
) {
ResolvedCall<? extends
FunctionDescriptor>
resolvedCall =
getSuperCall(
context.
bindingContext(),
enumEntry);
if (
resolvedCall == null) {
assert
enumEntry.
getInitializerList() == null : "Super call is missing on an enum entry with explicit initializer list " +
PsiUtilsKt.
getTextWithLocation(
enumEntry);
resolvedCall =
CallUtilKt.
getFunctionResolvedCallWithAssert(
enumEntry,
context.
bindingContext());
}
JsExpression nameArg = new
JsStringLiteral(
enumEntry.
getName());
JsExpression ordinalArg = new
JsIntLiteral(
ordinal);
List<
JsExpression>
additionalArgs =
Arrays.
asList(
nameArg,
ordinalArg);
JsExpression call =
CallTranslator.
translate(
context,
resolvedCall);
if (
call instanceof
JsInvocation) {
JsInvocation invocation = (
JsInvocation)
call;
invocation.
getArguments().
addAll(0,
additionalArgs);
}
else if (
call instanceof
JsNew) {
JsNew invocation = (
JsNew)
call;
invocation.
getArguments().
addAll(0,
additionalArgs);
}
return
call.
source(
enumEntry);
}
private void
mayBeAddCallToSuperMethod(
JsFunction initializer) {
if (
classDeclaration instanceof
KtClassOrObject &&
((
KtClassOrObject)
classDeclaration).
hasModifier(
KtTokens.
ENUM_KEYWORD)) {
addCallToSuperMethod(
Collections.
emptyList(),
initializer,
classDeclaration);
}
else if (
hasAncestorClass(
bindingContext(),
classDeclaration)) {
ResolvedCall<
FunctionDescriptor>
superCall =
getSuperCall(
bindingContext(),
classDeclaration);
if (
superCall == null) {
if (
DescriptorUtils.
isEnumEntry(
classDescriptor)) {
addCallToSuperMethod(
getAdditionalArgumentsForEnumConstructor(),
initializer,
classDeclaration);
}
return;
}
if (
JsDescriptorUtils.
isImmediateSubtypeOfError(
classDescriptor)) {
emulateSuperCallToNativeError(
context,
classDescriptor,
superCall, new
JsThisRef());
return;
}
if (
classDeclaration instanceof
KtEnumEntry) {
JsExpression expression =
CallTranslator.
translate(
context(),
superCall, null);
expression.
setSource(
classDeclaration);
JsExpression fixedInvocation =
AstUtilsKt.
toInvocationWith(
expression,
getAdditionalArgumentsForEnumConstructor(), 0, new
JsThisRef());
initFunction.
getBody().
getStatements().
add(
fixedInvocation.
makeStmt());
}
else {
List<
JsExpression>
arguments = new
ArrayList<>();
ConstructorDescriptor superDescriptor = (
ConstructorDescriptor)
superCall.
getResultingDescriptor();
if (
superDescriptor instanceof
TypeAliasConstructorDescriptor) {
superDescriptor = ((
TypeAliasConstructorDescriptor)
superDescriptor).
getUnderlyingConstructorDescriptor();
}
List<
DeclarationDescriptor>
superclassClosure =
context.
getClassOrConstructorClosure(
superDescriptor);
if (
superclassClosure != null) {
UsageTracker tracker =
context.
usageTracker();
if (
tracker != null) {
for (
DeclarationDescriptor capturedValue :
superclassClosure) {
tracker.
used(
capturedValue);
arguments.
add(
tracker.
getCapturedDescriptorToJsName().
get(
capturedValue).
makeRef());
}
}
}
if (
superCall.
getDispatchReceiver() != null) {
JsExpression receiver =
context.
getDispatchReceiver(
JsDescriptorUtils.
getReceiverParameterForReceiver(
superCall.
getDispatchReceiver()));
arguments.
add(
receiver);
}
if (!
DescriptorUtils.
isAnonymousObject(
classDescriptor)) {
arguments.
addAll(
CallArgumentTranslator.
translate(
superCall, null,
context()).
getTranslateArguments());
}
else {
for (
ValueParameterDescriptor parameter :
superDescriptor.
getValueParameters()) {
JsName parameterName =
context.
getNameForDescriptor(
parameter);
arguments.
add(
parameterName.
makeRef());
initializer.
getParameters().
add(new
JsParameter(
parameterName));
}
}
if (
superDescriptor.
isPrimary()) {
addCallToSuperMethod(
arguments,
initializer,
superCall.
getCall().
getCallElement());
}
else {
int
maxValueArgumentIndex = 0;
for (
ValueParameterDescriptor arg :
superCall.
getValueArguments().
keySet()) {
ResolvedValueArgument resolvedArg =
superCall.
getValueArguments().
get(
arg);
if (!(
resolvedArg instanceof
DefaultValueArgument)) {
maxValueArgumentIndex =
Math.
max(
maxValueArgumentIndex,
arg.
getIndex() + 1);
}
}
int
padSize =
superDescriptor.
getValueParameters().
size() -
maxValueArgumentIndex;
while (
padSize-- > 0) {
arguments.
add(
Namer.
getUndefinedExpression());
}
addCallToSuperSecondaryConstructor(
arguments,
superDescriptor);
}
}
}
}
public static void
emulateSuperCallToNativeError(
@
NotNull TranslationContext context,
@
NotNull ClassDescriptor classDescriptor,
@
NotNull ResolvedCall<? extends
FunctionDescriptor>
superCall,
@
NotNull JsExpression receiver
) {
ClassDescriptor superClass =
DescriptorUtilsKt.
getSuperClassOrAny(
classDescriptor);
JsExpression superClassRef =
ReferenceTranslator.
translateAsTypeReference(
superClass,
context);
JsExpression superInvocation = new
JsInvocation(
Namer.
getFunctionCallRef(
superClassRef),
receiver.
deepCopy());
List<
JsStatement>
statements =
context.
getCurrentBlock().
getStatements();
statements.
add(
JsAstUtils.
asSyntheticStatement(
superInvocation));
JsExpression messageArgument =
Namer.
getUndefinedExpression();
JsExpression causeArgument = new
JsNullLiteral();
for (
ValueParameterDescriptor param :
superCall.
getResultingDescriptor().
getValueParameters()) {
ResolvedValueArgument argument =
superCall.
getValueArguments().
get(
param);
if (!(
argument instanceof
ExpressionValueArgument)) continue;
ExpressionValueArgument exprArgument = (
ExpressionValueArgument)
argument;
assert
exprArgument.
getValueArgument() != null;
KtExpression value =
exprArgument.
getValueArgument().
getArgumentExpression();
assert
value != null;
JsExpression jsValue =
Translation.
translateAsExpression(
value,
context);
if (
KotlinBuiltIns.
isStringOrNullableString(
param.
getType())) {
messageArgument =
context.
cacheExpressionIfNeeded(
jsValue);
}
else if (
KotlinBuiltIns.
isThrowableOrNullableThrowable(
param.
getType())) {
causeArgument =
context.
cacheExpressionIfNeeded(
jsValue);
}
else {
statements.
add(
JsAstUtils.
asSyntheticStatement(
jsValue));
}
}
PropertyDescriptor messageProperty =
DescriptorUtils.
getPropertyByName(
classDescriptor.
getUnsubstitutedMemberScope(),
Name.
identifier("message"));
JsExpression messageRef =
pureFqn(
context.
getNameForBackingField(
messageProperty),
receiver.
deepCopy());
JsExpression messageIsUndefined =
JsAstUtils.
typeOfIs(
messageArgument, new
JsStringLiteral("undefined"));
JsExpression causeIsNull = new
JsBinaryOperation(
JsBinaryOperator.
NEQ,
causeArgument, new
JsNullLiteral());
JsExpression causeToStringCond =
JsAstUtils.
and(
messageIsUndefined,
causeIsNull);
JsExpression causeToString = new
JsInvocation(
pureFqn("toString",
Namer.
kotlinObject()),
causeArgument.
deepCopy());
JsExpression correctedMessage;
if (
causeArgument instanceof
JsNullLiteral) {
correctedMessage =
messageArgument.
deepCopy();
}
else {
if (
JsAstUtils.
isUndefinedExpression(
messageArgument)) {
causeToStringCond =
causeIsNull;
}
correctedMessage = new
JsConditional(
causeToStringCond,
causeToString,
messageArgument);
}
statements.
add(
JsAstUtils.
asSyntheticStatement(
JsAstUtils.
assignment(
messageRef,
correctedMessage)));
PropertyDescriptor causeProperty =
DescriptorUtils.
getPropertyByName(
classDescriptor.
getUnsubstitutedMemberScope(),
Name.
identifier("cause"));
JsExpression causeRef =
pureFqn(
context.
getNameForBackingField(
causeProperty),
receiver.
deepCopy());
statements.
add(
JsAstUtils.
asSyntheticStatement(
JsAstUtils.
assignment(
causeRef,
causeArgument.
deepCopy())));
}
@
NotNull
private
List<
JsExpression>
getAdditionalArgumentsForEnumConstructor() {
List<
JsExpression>
additionalArguments = new
ArrayList<>();
additionalArguments.
add(new
JsStringLiteral(
classDescriptor.
getName().
asString()));
additionalArguments.
add(new
JsIntLiteral(
ordinal));
return
additionalArguments;
}
private void
addCallToSuperMethod(@
NotNull List<
JsExpression>
arguments, @
NotNull JsFunction initializer, @
NotNull KtPureElement psi) {
if (
initializer.
getName() == null) {
JsName ref =
context().
scope().
declareName(
Namer.
CALLEE_NAME);
initializer.
setName(
ref);
}
ClassDescriptor superclassDescriptor =
DescriptorUtilsKt.
getSuperClassOrAny(
classDescriptor);
JsExpression superConstructorRef =
context().
getInnerReference(
superclassDescriptor);
JsInvocation call = new
JsInvocation(
Namer.
getFunctionCallRef(
superConstructorRef));
call.
setSource(
psi);
call.
getArguments().
add(new
JsThisRef());
call.
getArguments().
addAll(
arguments);
initFunction.
getBody().
getStatements().
add(
call.
makeStmt());
}
private void
addCallToSuperSecondaryConstructor(@
NotNull List<
JsExpression>
arguments, @
NotNull ConstructorDescriptor descriptor) {
JsExpression reference =
context.
getInnerReference(
descriptor);
JsInvocation call = new
JsInvocation(
reference);
call.
getArguments().
addAll(
arguments);
call.
getArguments().
add(new
JsThisRef());
initFunction.
getBody().
getStatements().
add(
call.
makeStmt());
}
@
NotNull
private
List<
JsParameter>
translatePrimaryConstructorParameters() {
List<
KtParameter>
parameterList =
getPrimaryConstructorParameters(
classDeclaration);
List<
JsParameter>
result = new
ArrayList<>();
for (
KtParameter jetParameter :
parameterList) {
result.
add(
translateParameter(
jetParameter));
}
return
result;
}
@
NotNull
private
JsParameter translateParameter(@
NotNull KtParameter jetParameter) {
DeclarationDescriptor parameterDescriptor =
getDescriptorForElement(
bindingContext(),
jetParameter);
JsName parameterName =
context().
getNameForDescriptor(
parameterDescriptor);
JsParameter jsParameter = new
JsParameter(
parameterName);
mayBeAddInitializerStatementForProperty(
jsParameter,
jetParameter);
return
jsParameter;
}
private void
mayBeAddInitializerStatementForProperty(@
NotNull JsParameter jsParameter,
@
NotNull KtParameter jetParameter) {
PropertyDescriptor propertyDescriptor =
getPropertyDescriptorForConstructorParameter(
bindingContext(),
jetParameter);
if (
propertyDescriptor == null) {
return;
}
JsExpression initialValueForProperty =
jsParameter.
getName().
makeRef();
MetadataProperties.
setType(
initialValueForProperty,
propertyDescriptor.
getType());
initialValueForProperty =
TranslationUtils.
coerce(
context(),
initialValueForProperty,
TranslationUtils.
getReturnTypeForCoercion(
propertyDescriptor));
addInitializerOrPropertyDefinition(
initialValueForProperty,
propertyDescriptor);
}
private void
addInitializerOrPropertyDefinition(@
NotNull JsExpression initialValue, @
NotNull PropertyDescriptor propertyDescriptor) {
initFunction.
getBody().
getStatements().
add(
InitializerUtils.
generateInitializerForProperty(
context(),
propertyDescriptor,
initialValue));
}
private void
addThrowableCall() {
if (!
JsDescriptorUtils.
isExceptionClass(
classDescriptor)) return;
if (
JsDescriptorUtils.
isImmediateSubtypeOfError(
classDescriptor)) {
ClassDescriptor superClass =
DescriptorUtilsKt.
getSuperClassOrAny(
classDescriptor);
JsExpression invocation = new
JsInvocation(
pureFqn("captureStack",
Namer.
kotlinObject()),
ReferenceTranslator.
translateAsTypeReference(
superClass,
context()),
new
JsThisRef());
initFunction.
getBody().
getStatements().
add(
JsAstUtils.
asSyntheticStatement(
invocation));
}
JsExpression nameLiteral = new
JsStringLiteral(
context.
getInnerNameForDescriptor(
classDescriptor).
getIdent());
JsExpression nameAssignment =
JsAstUtils.
assignment(
pureFqn("name", new
JsThisRef()),
nameLiteral);
initFunction.
getBody().
getStatements().
add(
JsAstUtils.
asSyntheticStatement(
nameAssignment));
}
}