From d17f095eecebad4e679f1f2fdca0acd68fe779a4 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 21 Apr 2011 14:23:11 +0200 Subject: [PATCH] namespace properties are mostly working --- .../jetbrains/jet/codegen/ClassCodegen.java | 6 +- .../jet/codegen/ExpressionCodegen.java | 27 +++++++-- .../jet/codegen/FunctionCodegen.java | 19 ++++--- .../jetbrains/jet/codegen/JetTypeMapper.java | 24 ++++++++ .../jet/codegen/NamespaceCodegen.java | 5 +- .../jet/codegen/PropertyCodegen.java | 56 ++++++++++++++++++- .../org/jetbrains/jet/codegen/StackValue.java | 40 +++++++++++++ idea/testData/codegen/fieldPropertyAccess.jet | 5 ++ idea/testData/codegen/fieldSetter.jet | 7 +++ .../jet/codegen/CodegenTestCase.java | 7 ++- .../jet/codegen/NamespaceGenTest.java | 3 +- .../jet/codegen/PropertyGenTest.java | 37 ++++++++++++ 12 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 idea/testData/codegen/fieldPropertyAccess.jet create mode 100644 idea/testData/codegen/fieldSetter.jet diff --git a/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java b/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java index 7b017409c52..e98c3d2e415 100644 --- a/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/ClassCodegen.java @@ -11,6 +11,7 @@ import org.jetbrains.jet.lang.types.JetType; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -151,8 +152,9 @@ public class ClassCodegen { } private void generateClassBody(JetClass aClass, ClassVisitor v, OwnerKind kind) { - final PropertyCodegen propertyCodegen = new PropertyCodegen(v); - final FunctionCodegen functionCodegen = new FunctionCodegen(v, JetStandardLibrary.getJetStandardLibrary(project), bindingContext); + final JetStandardLibrary standardLibrary = JetStandardLibrary.getJetStandardLibrary(project); + final FunctionCodegen functionCodegen = new FunctionCodegen(v, standardLibrary, bindingContext); + final PropertyCodegen propertyCodegen = new PropertyCodegen(v, standardLibrary, bindingContext, functionCodegen); for (JetDeclaration declaration : aClass.getDeclarations()) { if (declaration instanceof JetProperty) { diff --git a/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java b/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java index 5ca3805069a..8e9f88d72ec 100644 --- a/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/ExpressionCodegen.java @@ -370,6 +370,28 @@ public class ExpressionCodegen extends JetVisitor { final JetType outType = ((VariableDescriptor) descriptor).getOutType(); myStack.push(StackValue.local(index, typeMapper.mapType(outType))); } + else if (descriptor instanceof PropertyDescriptor) { + final PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor; + if (descriptor.getContainingDeclaration() instanceof NamespaceDescriptor) { + JetNamespace ns = (JetNamespace) bindingContext.getDeclarationPsiElement(descriptor.getContainingDeclaration()); + String owner = JetTypeMapper.jvmName(ns); + final JetType outType = ((VariableDescriptor) descriptor).getOutType(); + Method getter; + Method setter; + if (expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER) { + getter = null; + setter = null; + } + else { + getter = typeMapper.mapGetterSignature(propertyDescriptor); + setter = typeMapper.mapSetterSignature(propertyDescriptor); + } + myStack.push(StackValue.property(descriptor.getName(), owner, typeMapper.mapType(outType), getter, setter)); + } + else { + throw new UnsupportedOperationException("don't know how to generate non-namespace property reference " + descriptor); + } + } else { throw new UnsupportedOperationException("don't know how to generate reference " + descriptor); } @@ -887,10 +909,7 @@ public class ExpressionCodegen extends JetVisitor { int increment = op.getName().equals("inc") ? 1 : -1; if (operand instanceof JetReferenceExpression) { final int index = indexOfLocal((JetReferenceExpression) operand); - if (index < 0) { - throw new UnsupportedOperationException("don't know how to increment or decrement something which is not a local var"); - } - if (isIntPrimitive(asmType)) { + if (index >= 0 && isIntPrimitive(asmType)) { v.iinc(index, increment); return StackValue.local(index, asmType); } diff --git a/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java b/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java index 5fb6ab67194..5516b2e7961 100644 --- a/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/FunctionCodegen.java @@ -44,6 +44,12 @@ public class FunctionCodegen { } public void gen(JetFunction f, OwnerKind kind) { + Method method = typeMapper.mapSignature(f); + List paramDescrs = bindingContext.getFunctionDescriptor(f).getUnsubstitutedValueParameters(); + generateMethod(f, kind, method, paramDescrs); + } + + public void generateMethod(JetDeclarationWithBody f, OwnerKind kind, Method jvmSignature, List paramDescrs) { int flags = Opcodes.ACC_PUBLIC; // TODO. boolean isStatic = kind == OwnerKind.NAMESPACE; @@ -53,8 +59,7 @@ public class FunctionCodegen { boolean isAbstract = kind == OwnerKind.INTERFACE || bodyExpression == null; if (isAbstract) flags |= Opcodes.ACC_ABSTRACT; - Method method = typeMapper.mapSignature(f); - final MethodVisitor mv = v.visitMethod(flags, method.getName(), method.getDescriptor(), null, null); + final MethodVisitor mv = v.visitMethod(flags, jvmSignature.getName(), jvmSignature.getDescriptor(), null, null); if (kind != OwnerKind.INTERFACE) { mv.visitCode(); FrameMap frameMap = new FrameMap(); @@ -63,15 +68,13 @@ public class FunctionCodegen { frameMap.enterTemp(); // 0 slot for this } - List parameDescrs = bindingContext.getFunctionDescriptor(f).getUnsubstitutedValueParameters(); - - Type[] argTypes = method.getArgumentTypes(); - for (int i = 0; i < parameDescrs.size(); i++) { - ValueParameterDescriptor parameter = parameDescrs.get(i); + Type[] argTypes = jvmSignature.getArgumentTypes(); + for (int i = 0; i < paramDescrs.size(); i++) { + ValueParameterDescriptor parameter = paramDescrs.get(i); frameMap.enter(parameter, argTypes[i].getSize()); } - ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, method.getReturnType()); + ExpressionCodegen codegen = new ExpressionCodegen(mv, bindingContext, frameMap, typeMapper, jvmSignature.getReturnType()); bodyExpression.accept(codegen); generateReturn(mv, bodyExpression, codegen); mv.visitMaxs(0, 0); diff --git a/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java b/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java index 38a20e3cd10..ba0c0ade047 100644 --- a/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java +++ b/idea/src/org/jetbrains/jet/codegen/JetTypeMapper.java @@ -2,7 +2,9 @@ package org.jetbrains.jet.codegen; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.psi.JetFunction; +import org.jetbrains.jet.lang.psi.JetNamespace; import org.jetbrains.jet.lang.psi.JetParameter; import org.jetbrains.jet.lang.psi.JetTypeReference; import org.jetbrains.jet.lang.resolve.BindingContext; @@ -32,6 +34,10 @@ public class JetTypeMapper { return Type.getType("L" + jvmName(psiClass) + ";"); } + static String jvmName(JetNamespace namespace) { + return NamespaceCodegen.getJVMClassName(namespace.getFQName()); + } + public Type mapType(final JetType jetType) { if (jetType.equals(JetStandardClasses.getUnitType())) { return Type.VOID_TYPE; @@ -130,4 +136,22 @@ public class JetTypeMapper { } return new Method(f.getName(), returnType, parameterTypes); } + + @Nullable + public Method mapGetterSignature(PropertyDescriptor descriptor) { + if (descriptor.getGetter() == null) { + return null; + } + Type returnType = mapType(descriptor.getOutType()); + return new Method(PropertyCodegen.getterName(descriptor.getName()), returnType, new Type[0]); + } + + @Nullable + public Method mapSetterSignature(PropertyDescriptor descriptor) { + if (descriptor.getSetter() == null) { + return null; + } + Type paramType = mapType(descriptor.getInType()); + return new Method(PropertyCodegen.setterName(descriptor.getName()), Type.VOID_TYPE, new Type[] { paramType }); + } } diff --git a/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java b/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java index 407ff9f02c2..5168804035b 100644 --- a/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/NamespaceCodegen.java @@ -35,8 +35,9 @@ public class NamespaceCodegen { public void generate(JetNamespace namespace) { BindingContext bindingContext = AnalyzingUtils.analyzeNamespace(namespace, ErrorHandler.THROW_EXCEPTION); - final PropertyCodegen propertyCodegen = new PropertyCodegen(v); - final FunctionCodegen functionCodegen = new FunctionCodegen(v, JetStandardLibrary.getJetStandardLibrary(project), bindingContext); + final JetStandardLibrary standardLibrary = JetStandardLibrary.getJetStandardLibrary(project); + final FunctionCodegen functionCodegen = new FunctionCodegen(v, standardLibrary, bindingContext); + final PropertyCodegen propertyCodegen = new PropertyCodegen(v, standardLibrary, bindingContext, functionCodegen); final ClassCodegen classCodegen = codegens.forClass(bindingContext); for (JetDeclaration declaration : namespace.getDeclarations()) { diff --git a/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java b/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java index 2e357495772..8a8def24bef 100644 --- a/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java +++ b/idea/src/org/jetbrains/jet/codegen/PropertyCodegen.java @@ -1,16 +1,31 @@ package org.jetbrains.jet.codegen; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.jet.lang.psi.JetConstantExpression; +import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetProperty; +import org.jetbrains.jet.lang.psi.JetPropertyAccessor; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.types.*; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.Collections; /** * @author max */ public class PropertyCodegen { + private final BindingContext context; + private final FunctionCodegen functionCodegen; private final ClassVisitor v; + private final JetTypeMapper mapper; - public PropertyCodegen(ClassVisitor v) { + public PropertyCodegen(ClassVisitor v, JetStandardLibrary standardLibrary, BindingContext context, FunctionCodegen functionCodegen) { this.v = v; + this.context = context; + this.functionCodegen = functionCodegen; + this.mapper = new JetTypeMapper(standardLibrary, context); } public void genInNamespace(JetProperty p) { @@ -30,6 +45,45 @@ public class PropertyCodegen { } public void gen(JetProperty p, OwnerKind kind) { + if (kind == OwnerKind.NAMESPACE) { + final VariableDescriptor descriptor = context.getVariableDescriptor(p); + if (!(descriptor instanceof PropertyDescriptor)) { + throw new UnsupportedOperationException("expect a property to have a property descriptor"); + } + final PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor; + if (context.hasBackingField(propertyDescriptor)) { + Object value = null; + final JetExpression initializer = p.getInitializer(); + if (initializer != null) { + if (initializer instanceof JetConstantExpression) { + value = ((JetConstantExpression) initializer).getValue(); + } + } + v.visitField(Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE, + p.getName(), + mapper.mapType(descriptor.getOutType()).getDescriptor(), + null, value); + } + final JetPropertyAccessor getter = p.getGetter(); + if (getter != null) { + functionCodegen.generateMethod(getter, kind, mapper.mapGetterSignature(propertyDescriptor), + Collections.emptyList()); + } + final JetPropertyAccessor setter = p.getSetter(); + if (setter != null) { + final PropertySetterDescriptor setterDescriptor = propertyDescriptor.getSetter(); + assert setterDescriptor != null; + functionCodegen.generateMethod(setter, kind, mapper.mapSetterSignature(propertyDescriptor), + setterDescriptor.getUnsubstitutedValueParameters()); + } + } + } + public static String getterName(String propertyName) { + return "get" + StringUtil.capitalizeWithJavaBeanConvention(propertyName); + } + + public static String setterName(String propertyName) { + return "set" + StringUtil.capitalizeWithJavaBeanConvention(propertyName); } } diff --git a/idea/src/org/jetbrains/jet/codegen/StackValue.java b/idea/src/org/jetbrains/jet/codegen/StackValue.java index 29fe0156015..06c70534c29 100644 --- a/idea/src/org/jetbrains/jet/codegen/StackValue.java +++ b/idea/src/org/jetbrains/jet/codegen/StackValue.java @@ -6,6 +6,7 @@ import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.InstructionAdapter; +import org.objectweb.asm.commons.Method; /** * @author yole @@ -70,6 +71,10 @@ public abstract class StackValue { return new Field(type, owner, name, isStatic); } + public static StackValue property(String name, String owner, Type type, Method getter, Method setter) { + return new Property(name, owner, getter, setter, type); + } + private static void box(final Type type, InstructionAdapter v) { if (type == Type.INT_TYPE) { v.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); @@ -368,4 +373,39 @@ public abstract class StackValue { v.visitFieldInsn(isStatic ? Opcodes.PUTSTATIC : Opcodes.PUTFIELD, owner, name, this.type.getDescriptor()); } } + + private static class Property extends StackValue { + private final String name; + private final String owner; + private final Method getter; + private final Method setter; + + public Property(String name, String owner, Method getter, Method setter, Type type) { + super(type); + this.name = name; + this.owner = owner; + this.getter = getter; + this.setter = setter; + } + + @Override + public void put(Type type, InstructionAdapter v) { + if (getter == null) { + v.visitFieldInsn(Opcodes.GETSTATIC, owner, name, type.getDescriptor()); + } + else { + v.invokestatic(owner, getter.getName(), getter.getDescriptor()); + } + } + + @Override + public void store(InstructionAdapter v) { + if (setter == null) { + v.visitFieldInsn(Opcodes.PUTSTATIC, owner, name, type.getDescriptor()); + } + else { + v.invokestatic(owner, setter.getName(), setter.getDescriptor()); + } + } + } } diff --git a/idea/testData/codegen/fieldPropertyAccess.jet b/idea/testData/codegen/fieldPropertyAccess.jet new file mode 100644 index 00000000000..f8184c004e4 --- /dev/null +++ b/idea/testData/codegen/fieldPropertyAccess.jet @@ -0,0 +1,5 @@ +private var x = 0; + +fun increment(): Int { + return ++x; +} diff --git a/idea/testData/codegen/fieldSetter.jet b/idea/testData/codegen/fieldSetter.jet new file mode 100644 index 00000000000..84ee1fc0290 --- /dev/null +++ b/idea/testData/codegen/fieldSetter.jet @@ -0,0 +1,7 @@ +var collector: String = "" + set(it) { $collector = $collector + it } + +fun append(s: String): String { + collector = s; + return collector; +} diff --git a/idea/tests/org/jetbrains/jet/codegen/CodegenTestCase.java b/idea/tests/org/jetbrains/jet/codegen/CodegenTestCase.java index dc692341c90..97958b34e07 100644 --- a/idea/tests/org/jetbrains/jet/codegen/CodegenTestCase.java +++ b/idea/tests/org/jetbrains/jet/codegen/CodegenTestCase.java @@ -42,7 +42,7 @@ public abstract class CodegenTestCase extends LightCodeInsightFixtureTestCase { return answer.toString(); } - private Class generateNamespaceClass() { + protected Class generateNamespaceClass() { JetFile jetFile = (JetFile) myFixture.getFile(); final JetNamespace namespace = jetFile.getRootNamespace(); String fqName = NamespaceCodegen.getJVMClassName(namespace.getFQName()).replace("/", "."); @@ -89,6 +89,11 @@ public abstract class CodegenTestCase extends LightCodeInsightFixtureTestCase { throw new IllegalArgumentException("couldn't find method " + name); } + protected void assertIsCurrentTime(long returnValue) { + long currentTime = System.currentTimeMillis(); + assertTrue(Math.abs(returnValue - currentTime) <= 1L); + } + private static class MyClassLoader extends ClassLoader { public MyClassLoader(ClassLoader parent) { super(parent); diff --git a/idea/tests/org/jetbrains/jet/codegen/NamespaceGenTest.java b/idea/tests/org/jetbrains/jet/codegen/NamespaceGenTest.java index 16698be6a7a..abba7b42a1d 100644 --- a/idea/tests/org/jetbrains/jet/codegen/NamespaceGenTest.java +++ b/idea/tests/org/jetbrains/jet/codegen/NamespaceGenTest.java @@ -56,8 +56,7 @@ public class NamespaceGenTest extends CodegenTestCase { System.out.println(generateToText()); final Method main = generateFunction(); final long returnValue = (Long) main.invoke(null); - long currentTime = System.currentTimeMillis(); - assertTrue(Math.abs(returnValue - currentTime) <= 1L); + assertIsCurrentTime(returnValue); } public void testIdentityHashCode() throws Exception { diff --git a/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java b/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java index 12579ca1705..5fe4976030e 100644 --- a/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java +++ b/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java @@ -1,5 +1,9 @@ package org.jetbrains.jet.codegen; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + /** * @author yole */ @@ -8,6 +12,39 @@ public class PropertyGenTest extends CodegenTestCase { loadFile("privateVal.jet"); System.out.println(generateToText()); // TODO + } + public void testPropertyInNamespace() throws Exception { + loadText("private val x = 239"); + final Class nsClass = generateNamespaceClass(); + final Field[] fields = nsClass.getDeclaredFields(); + assertEquals(1, fields.length); + final Field field = fields[0]; + field.setAccessible(true); + assertEquals("x", field.getName()); + assertEquals(Modifier.PRIVATE | Modifier.STATIC, field.getModifiers()); + assertEquals(239, field.get(null)); + } + + public void testFieldPropertyAccess() throws Exception { + loadFile("fieldPropertyAccess.jet"); + final Method method = generateFunction(); + assertEquals(1, method.invoke(null)); + assertEquals(2, method.invoke(null)); + } + + public void testFieldGetter() throws Exception { + loadText("val now: Long get() = System.currentTimeMillis(); fun foo() = now"); + final Method method = generateFunction("foo"); + assertIsCurrentTime((Long) method.invoke(null)); + } + + public void testFieldSetter() throws Exception { + loadFile("fieldSetter.jet"); + System.out.println(generateToText()); + final Method method = generateFunction("append"); + method.invoke(null, "IntelliJ "); + String value = (String) method.invoke(null, "IDEA"); + assertEquals(value, "IntelliJ IDEA"); } }