From 58f50a80464c06dedd79f9c9c901c6711894ccf6 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 26 May 2011 19:07:50 +0400 Subject: [PATCH] initial support for generating extension functions --- .../jetbrains/jet/codegen/ClassCodegen.java | 2 +- .../jet/codegen/ExpressionCodegen.java | 50 +++++++++++++------ .../jet/codegen/FunctionCodegen.java | 17 +++++-- .../jetbrains/jet/codegen/JetTypeMapper.java | 13 +++-- .../jet/codegen/NamespaceCodegen.java | 3 +- .../jet/codegen/PropertyCodegen.java | 4 +- .../codegen/extensionFunctions/simple.jet | 3 ++ .../jet/codegen/ExtensionFunctionsTest.java | 20 ++++++++ 8 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 idea/testData/codegen/extensionFunctions/simple.jet create mode 100644 idea/tests/org/jetbrains/jet/codegen/ExtensionFunctionsTest.java diff --git a/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java b/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java index ce5a8a0aa1b..c1deefae4b5 100644 --- a/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java @@ -175,7 +175,7 @@ public class ClassCodegen { frameMap.enterTemp(); // this final InstructionAdapter iv = new InstructionAdapter(mv); - ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, Type.VOID_TYPE, classDescriptor, kind); + ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, null, Type.VOID_TYPE, classDescriptor, kind); String classname = typeMapper.jvmName(classDescriptor, kind); final Type classType = Type.getType("L" + classname + ";"); diff --git a/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java b/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java index 1344ca9f5ff..b5e4cc2578a 100644 --- a/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java @@ -66,6 +66,7 @@ public class ExpressionCodegen extends JetVisitor { private final InstructionAdapter v; private final FrameMap myMap; private final JetTypeMapper typeMapper; + private final JetType receiverType; private final Type returnType; private final DeclarationDescriptor contextType; private final OwnerKind contextKind; @@ -75,11 +76,13 @@ public class ExpressionCodegen extends JetVisitor { BindingContext bindingContext, FrameMap myMap, JetTypeMapper typeMapper, + JetType receiverType, Type returnType, DeclarationDescriptor contextType, OwnerKind contextKind) { this.myMap = myMap; this.typeMapper = typeMapper; + this.receiverType = receiverType; this.returnType = returnType; this.contextType = contextType; this.contextKind = contextKind; @@ -107,11 +110,15 @@ public class ExpressionCodegen extends JetVisitor { int oldStackDepth = myStack.size(); gen(expr); if (myStack.size() == oldStackDepth+1) { - StackValue value = myStack.pop(); - value.put(type, v); + putTopOfStack(type); } } + private void putTopOfStack(Type type) { + StackValue value = myStack.pop(); + value.put(type, v); + } + public void genToJVMStack(JetExpression expr) { gen(expr, expressionType(expr)); } @@ -526,8 +533,7 @@ public class ExpressionCodegen extends JetVisitor { public void returnTopOfStack() { if (myStack.size() > 0) { - StackValue value = myStack.pop(); - value.put(returnType, v); + putTopOfStack(returnType); v.areturn(returnType); } } @@ -1354,23 +1360,37 @@ public class ExpressionCodegen extends JetVisitor { @Override public void visitThisExpression(JetThisExpression expression) { - thisToStack(); + generateThis(); } public void thisToStack() { - if (contextKind == OwnerKind.NAMESPACE) { - throw new UnsupportedOperationException("Cannot generate this expression in top level context"); - } + generateThis(); + putTopOfStack(JetTypeMapper.TYPE_OBJECT); + } - ClassDescriptor contextClass = (ClassDescriptor) contextType; - if (contextKind == OwnerKind.IMPLEMENTATION) { - v.load(0, JetTypeMapper.jetImplementationType(contextClass)); - } - else if (contextKind == OwnerKind.DELEGATING_IMPLEMENTATION) { - v.getfield(typeMapper.jvmName(contextClass, contextKind), "$this", JetTypeMapper.jetInterfaceType(contextClass).getDescriptor()); + private void generateThis() { + if (contextKind == OwnerKind.NAMESPACE) { + if (receiverType != null) { + myStack.push(StackValue.local(0, typeMapper.mapType(receiverType))); + } + else { + throw new UnsupportedOperationException("Cannot generate this expression in top level context"); + } } else { - throw new UnsupportedOperationException("Unknown kind: " + contextKind); + ClassDescriptor contextClass = (ClassDescriptor) contextType; + if (contextKind == OwnerKind.IMPLEMENTATION) { + myStack.push(StackValue.local(0, JetTypeMapper.jetImplementationType(contextClass))); + } + else if (contextKind == OwnerKind.DELEGATING_IMPLEMENTATION) { + myStack.push(StackValue.field(JetTypeMapper.jetInterfaceType(contextClass), + typeMapper.jvmName(contextClass, contextKind), + "$this", + false)); + } + else { + throw new UnsupportedOperationException("Unknown kind: " + contextKind); + } } } diff --git a/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java b/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java index 311924650b7..1cfca3069c1 100644 --- a/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java @@ -1,10 +1,12 @@ package org.jetbrains.jet.codegen; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.types.JetStandardLibrary; +import org.jetbrains.jet.lang.types.JetType; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -31,12 +33,18 @@ public class FunctionCodegen { } public void gen(JetFunction f, OwnerKind kind) { + final JetTypeReference receiverTypeRef = f.getReceiverTypeRef(); + final JetType receiverType = receiverTypeRef == null ? null : bindingContext.resolveTypeReference(receiverTypeRef); Method method = typeMapper.mapSignature(f); List paramDescrs = bindingContext.getFunctionDescriptor(f).getUnsubstitutedValueParameters(); - generateMethod(f, kind, method, paramDescrs); + generateMethod(f, kind, method, receiverType, paramDescrs); } - public void generateMethod(JetDeclarationWithBody f, OwnerKind kind, Method jvmSignature, List paramDescrs) { + public void generateMethod(JetDeclarationWithBody f, + OwnerKind kind, + Method jvmSignature, + @Nullable JetType receiverType, + List paramDescrs) { int flags = Opcodes.ACC_PUBLIC; // TODO. boolean isStatic = kind == OwnerKind.NAMESPACE; @@ -55,7 +63,7 @@ public class FunctionCodegen { mv.visitCode(); FrameMap frameMap = new FrameMap(); - if (kind != OwnerKind.NAMESPACE) { + if (kind != OwnerKind.NAMESPACE || receiverType != null) { frameMap.enterTemp(); // 0 slot for this } @@ -65,7 +73,8 @@ public class FunctionCodegen { frameMap.enter(parameter, argTypes[i].getSize()); } - ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, jvmSignature.getReturnType(), contextDescriptor, kind); + ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, receiverType, + jvmSignature.getReturnType(), contextDescriptor, kind); if (kind instanceof OwnerKind.DelegateKind) { OwnerKind.DelegateKind dk = (OwnerKind.DelegateKind) kind; InstructionAdapter iv = new InstructionAdapter(mv); diff --git a/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java b/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java index 71c516ba0c2..fdcfc61b47d 100644 --- a/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java +++ b/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java @@ -206,10 +206,15 @@ public class JetTypeMapper { } public Method mapSignature(JetFunction f) { + final JetTypeReference receiverTypeRef = f.getReceiverTypeRef(); + final JetType receiverType = receiverTypeRef == null ? null : bindingContext.resolveTypeReference(receiverTypeRef); final List parameters = f.getValueParameters(); - Type[] parameterTypes = new Type[parameters.size()]; - for (int i = 0; i < parameters.size(); i++) { - parameterTypes[i] = mapType(bindingContext.resolveTypeReference(parameters.get(i).getTypeReference())); + List parameterTypes = new ArrayList(); + if (receiverType != null) { + parameterTypes.add(mapType(receiverType)); + } + for (JetParameter parameter : parameters) { + parameterTypes.add(mapType(bindingContext.resolveTypeReference(parameter.getTypeReference()))); } final JetTypeReference returnTypeRef = f.getReturnTypeRef(); Type returnType; @@ -221,7 +226,7 @@ public class JetTypeMapper { else { returnType = mapType(bindingContext.resolveTypeReference(returnTypeRef)); } - return new Method(f.getName(), returnType, parameterTypes); + return new Method(f.getName(), returnType, parameterTypes.toArray(new Type[parameterTypes.size()])); } public Method mapGetterSignature(PropertyDescriptor descriptor) { diff --git a/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java b/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java index 61dc8c188d0..12d40dff1f4 100644 --- a/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java @@ -13,7 +13,6 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.InstructionAdapter; -import org.objectweb.asm.commons.Method; /** * @author max @@ -72,7 +71,7 @@ public class NamespaceCodegen { FrameMap frameMap = new FrameMap(); JetTypeMapper typeMapper = new JetTypeMapper(JetStandardLibrary.getJetStandardLibrary(namespace.getProject()), bindingContext); - ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, Type.VOID_TYPE, null, OwnerKind.NAMESPACE); + ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, null, Type.VOID_TYPE, null, OwnerKind.NAMESPACE); for (JetDeclaration declaration : namespace.getDeclarations()) { if (declaration instanceof JetProperty) { diff --git a/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java b/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java index dcdead60d80..36b9876354a 100644 --- a/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java @@ -96,7 +96,7 @@ public class PropertyCodegen { if (getter != null) { if (getter.getBodyExpression() != null) { functionCodegen.generateMethod(getter, kind, mapper.mapGetterSignature(propertyDescriptor), - Collections.emptyList()); + null, Collections.emptyList()); } else if (!getter.hasModifier(JetTokens.PRIVATE_KEYWORD)) { generateDefaultGetter(p, getter, kind); @@ -118,7 +118,7 @@ public class PropertyCodegen { final PropertySetterDescriptor setterDescriptor = propertyDescriptor.getSetter(); assert setterDescriptor != null; functionCodegen.generateMethod(setter, kind, mapper.mapSetterSignature(propertyDescriptor), - setterDescriptor.getUnsubstitutedValueParameters()); + null, setterDescriptor.getUnsubstitutedValueParameters()); } else if (!p.hasModifier(JetTokens.PRIVATE_KEYWORD)) { generateDefaultSetter(p, setter, kind); diff --git a/idea/testData/codegen/extensionFunctions/simple.jet b/idea/testData/codegen/extensionFunctions/simple.jet new file mode 100644 index 00000000000..dcc44085766 --- /dev/null +++ b/idea/testData/codegen/extensionFunctions/simple.jet @@ -0,0 +1,3 @@ +fun StringBuilder.first() = this.charAt(0) + +fun foo() = new StringBuilder("foo").first() diff --git a/idea/tests/org/jetbrains/jet/codegen/ExtensionFunctionsTest.java b/idea/tests/org/jetbrains/jet/codegen/ExtensionFunctionsTest.java new file mode 100644 index 00000000000..59d78daf88a --- /dev/null +++ b/idea/tests/org/jetbrains/jet/codegen/ExtensionFunctionsTest.java @@ -0,0 +1,20 @@ +package org.jetbrains.jet.codegen; + +import java.lang.reflect.Method; + +/** + * @author yole + */ +public class ExtensionFunctionsTest extends CodegenTestCase { + @Override + protected String getPrefix() { + return "extensionFunctions"; + } + + public void testSimple() throws Exception { + loadFile(); + final Method foo = generateFunction("foo"); + final Character c = (Character) foo.invoke(null); + assertEquals('f', c.charValue()); + } +}