From 7ec6adec98da65627a87898c63840b0562b7227b Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Fri, 4 Feb 2011 17:17:46 +0300 Subject: [PATCH] Basic support for blocks and local variables --- .../jet/lang/psi/JetBlockExpression.java | 5 +- .../jetbrains/jet/lang/psi/JetChangeUtil.java | 5 +- .../psi/JetFunctionLiteralExpression.java | 8 ++- .../jet/lang/resolve/AccumulatingScope.java | 32 +++++++++ .../lang/resolve/ClassDescriptorResolver.java | 24 ++++++- .../jet/lang/resolve/JetScopeAdapter.java | 2 + .../jet/lang/types/JetStandardClasses.java | 51 ++++++++++++++ .../jet/lang/types/JetTypeChecker.java | 68 ++++++++++++------- .../jet/types/JetTypeCheckerTest.java | 42 ++++-------- 9 files changed, 176 insertions(+), 61 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/lang/resolve/AccumulatingScope.java diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java b/idea/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java index a7cbbb246ef..1741591cdec 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java @@ -19,7 +19,8 @@ public class JetBlockExpression extends JetExpression { visitor.visitBlockExpression(this); } - public List getStatements() { - return Arrays.asList(findChildrenByClass(JetExpression.class)); + @NotNull + public List getStatements() { + return Arrays.asList(findChildrenByClass(JetElement.class)); } } diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetChangeUtil.java b/idea/src/org/jetbrains/jet/lang/psi/JetChangeUtil.java index ca854caafd5..22623f54bc5 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetChangeUtil.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetChangeUtil.java @@ -46,8 +46,9 @@ public class JetChangeUtil { JetNamespace rootNamespace = file.getRootNamespace(); List dcls = rootNamespace.getDeclarations(); assert dcls.size() == 1; - //noinspection unchecked - return (T) dcls.get(0); + @SuppressWarnings("unchecked") + T result = (T) dcls.get(0); + return result; } public static PsiElement createNameIdentifier(Project project, String name) { diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetFunctionLiteralExpression.java b/idea/src/org/jetbrains/jet/lang/psi/JetFunctionLiteralExpression.java index 5d255070594..91ac518b5fb 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetFunctionLiteralExpression.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetFunctionLiteralExpression.java @@ -45,8 +45,8 @@ public class JetFunctionLiteralExpression extends JetExpression { } @NotNull - public List getBody() { - return PsiTreeUtil.getChildrenOfTypeAsList(getBodyNode().getPsi(), JetExpression.class); + public List getBody() { + return PsiTreeUtil.getChildrenOfTypeAsList(getBodyNode().getPsi(), JetElement.class); } @Nullable @@ -81,4 +81,8 @@ public class JetFunctionLiteralExpression extends JetExpression { return null; } + + public boolean hasParameterSpecification() { + return findChildByType(JetTokens.DOUBLE_ARROW) != null; + } } diff --git a/idea/src/org/jetbrains/jet/lang/resolve/AccumulatingScope.java b/idea/src/org/jetbrains/jet/lang/resolve/AccumulatingScope.java new file mode 100644 index 00000000000..c6ce9c7e553 --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/resolve/AccumulatingScope.java @@ -0,0 +1,32 @@ +package org.jetbrains.jet.lang.resolve; + +import org.jetbrains.jet.lang.types.PropertyDescriptor; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author abreslav + */ +public class AccumulatingScope extends JetScopeAdapter { + private final Map properties = new HashMap(); + + public AccumulatingScope(JetScope outerScope) { + super(outerScope); + } + + public void addPropertyDescriptor(PropertyDescriptor propertyDescriptor) { + if (properties.put(propertyDescriptor.getName(), propertyDescriptor) != null) { + throw new IllegalArgumentException("Duplicate property: " + propertyDescriptor.getName()); + } + } + + @Override + public PropertyDescriptor getProperty(String name) { + PropertyDescriptor propertyDescriptor = properties.get(name); + if (propertyDescriptor == null) { + return super.getProperty(name); + } + return propertyDescriptor; + } +} diff --git a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java index ec87ecf28c9..4e613ed1e6c 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java @@ -77,8 +77,8 @@ public class ClassDescriptorResolver { } private static final class TypeParameterExtensibleScope extends JetScopeAdapter { - private final Map typeParameterDescriptors = new HashMap(); + private final Map typeParameterDescriptors = new HashMap(); private TypeParameterExtensibleScope(JetScope scope) { super(scope); } @@ -99,8 +99,8 @@ public class ClassDescriptorResolver { } return super.getTypeParameterDescriptor(name); } - } + } @NotNull public PropertyDescriptor resolvePropertyDescriptor(@NotNull JetScope scope, @NotNull JetParameter parameter) { return new PropertyDescriptor( @@ -108,4 +108,24 @@ public class ClassDescriptorResolver { parameter.getName(), TypeResolver.INSTANCE.resolveType(scope, parameter.getTypeReference())); } + + public static PropertyDescriptor resolvePropertyDescriptor(@NotNull JetScope scope, JetProperty property) { + // TODO : receiver? + JetTypeReference propertyTypeRef = property.getPropertyTypeRef(); + + Type type; + if (propertyTypeRef == null) { + JetExpression initializer = property.getInitializer(); + assert initializer != null; + // TODO : ??? Fix-point here: what if we have something like "val a = foo {a.bar()}" + type = JetTypeChecker.INSTANCE.getType(scope, initializer, false); + } else { + type = TypeResolver.INSTANCE.resolveType(scope, propertyTypeRef); + } + + return new PropertyDescriptor( + AttributeResolver.INSTANCE.resolveAttributes(property.getModifierList()), + property.getName(), + type); + } } diff --git a/idea/src/org/jetbrains/jet/lang/resolve/JetScopeAdapter.java b/idea/src/org/jetbrains/jet/lang/resolve/JetScopeAdapter.java index 339f18b972c..6de917a542e 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/JetScopeAdapter.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/JetScopeAdapter.java @@ -1,5 +1,6 @@ package org.jetbrains.jet.lang.resolve; +import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.types.*; /** @@ -12,6 +13,7 @@ public class JetScopeAdapter implements JetScope { this.scope = scope; } + @NotNull @Override public Type getThisType() { return scope.getThisType(); diff --git a/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java b/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java index 5ec30be0607..92ecc7aaeef 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java @@ -2,7 +2,11 @@ package org.jetbrains.jet.lang.types; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.resolve.JetScope; +import org.jetbrains.jet.lang.resolve.JetScopeImpl; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.*; /** @@ -135,6 +139,43 @@ public class JetStandardClasses { Collections.emptyList(), TypeMemberDomain.EMPTY); + private static final Map CLASS_MAP = new HashMap(); + static { + Field[] declaredFields = JetStandardClasses.class.getDeclaredFields(); + for (Field field : declaredFields) { + if ((field.getModifiers() & Modifier.STATIC) == 0) { + continue; + } + Class type = field.getType(); + if (type == ClassDescriptor.class) { + try { + ClassDescriptor descriptor = (ClassDescriptor) field.get(null); + CLASS_MAP.put(descriptor.getName(), descriptor); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + } else if (type.isArray() && type.getComponentType() == ClassDescriptor.class) { + try { + ClassDescriptor[] array = (ClassDescriptor[]) field.get(null); + for (ClassDescriptor descriptor : array) { + CLASS_MAP.put(descriptor.getName(), descriptor); + } + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + } + CLASS_MAP.put("Unit", getTuple(0)); + } + + @NotNull + public static final JetScope STANDARD_CLASSES = new JetScopeImpl() { + @Override + public ClassDescriptor getClass(String name) { + return CLASS_MAP.get(name); + } + }; + @NotNull public static ClassDescriptor getAny() { return ANY; @@ -204,6 +245,16 @@ public class JetStandardClasses { return TUPLE[size]; } + @NotNull + public static ClassDescriptor getFunction(int parameterCount) { + return FUNCTION[parameterCount]; + } + + @NotNull + public static ClassDescriptor getReceiverFunction(int parameterCount) { + return RECEIVER_FUNCTION[parameterCount]; + } + public static Type getIntType() { return INT_TYPE; } diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java index 5cfb87342b2..5365bd98530 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java @@ -5,10 +5,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetNodeTypes; import org.jetbrains.jet.lang.psi.*; -import org.jetbrains.jet.lang.resolve.ClassDescriptorResolver; -import org.jetbrains.jet.lang.resolve.JetScope; -import org.jetbrains.jet.lang.resolve.JetScopeAdapter; -import org.jetbrains.jet.lang.resolve.TypeResolver; +import org.jetbrains.jet.lang.resolve.*; import org.jetbrains.jet.lexer.JetTokens; import java.util.*; @@ -33,7 +30,7 @@ public class JetTypeChecker { : declaration : "namespace" // for the root namespace */ - public Type getType(@NotNull final JetScope scope, @NotNull JetExpression expression) { + public Type getType(@NotNull final JetScope scope, @NotNull JetExpression expression, final boolean preferBlock) { final Type[] result = new Type[1]; expression.accept(new JetVisitor() { @Override @@ -48,6 +45,11 @@ public class JetTypeChecker { @Override public void visitFunctionLiteralExpression(JetFunctionLiteralExpression expression) { + if (preferBlock && !expression.hasParameterSpecification()) { + result[0] = getBlockReturnedType(scope, expression.getBody()); + return; + } + JetTypeReference returnTypeRef = expression.getReturnTypeRef(); JetTypeReference receiverTypeRef = expression.getReceiverTypeRef(); @@ -58,7 +60,7 @@ public class JetTypeChecker { thisType = scope.getThisType(); } - List body = expression.getBody(); + List body = expression.getBody(); final Map parameterDescriptors = new HashMap(); List parameterTypes = new ArrayList(); for (JetParameter parameter : expression.getParameters()) { @@ -73,10 +75,8 @@ public class JetTypeChecker { Type returnType; if (returnTypeRef != null) { returnType = TypeResolver.INSTANCE.resolveType(scope, returnTypeRef); - } else if (body.isEmpty()) { - returnType = JetStandardClasses.getUnitType(); } else { - returnType = getType(new JetScopeAdapter(scope) { + returnType = getBlockReturnedType(new JetScopeAdapter(scope) { @Override public Type getThisType() { return thisType; @@ -90,14 +90,14 @@ public class JetTypeChecker { } return propertyDescriptor; } - }, body.get(body.size() - 1)); + }, body); } result[0] = JetStandardClasses.getFunctionType(null, receiverTypeRef == null ? null : thisType, parameterTypes, returnType); } @Override public void visitParenthesizedExpression(JetParenthesizedExpression expression) { - result[0] = getType(scope, expression.getExpression()); + result[0] = getType(scope, expression.getExpression(), false); } @Override @@ -157,7 +157,7 @@ public class JetTypeChecker { @Override public void visitBinaryWithTypeRHSExpression(JetBinaryExpressionWithTypeRHS expression) { if (expression.getOperationSign() == JetTokens.COLON) { - Type actualType = getType(scope, expression.getLeft()); + Type actualType = getType(scope, expression.getLeft(), false); Type expectedType = TypeResolver.INSTANCE.resolveType(scope, expression.getRight()); if (isSubtypeOf(actualType, expectedType)) { result[0] = expectedType; @@ -179,8 +179,8 @@ public class JetTypeChecker { // TODO : type-check the branch result[0] = JetStandardClasses.getUnitType(); } else { - Type thenType = getType(scope, expression.getThen()); - Type elseType = getType(scope, elseBranch); + Type thenType = getType(scope, expression.getThen(), true); + Type elseType = getType(scope, elseBranch, true); result[0] = commonSupertype(Arrays.asList(thenType, elseType)); } } @@ -202,12 +202,12 @@ public class JetTypeChecker { if (finallyBlock == null) { for (JetCatchClause catchClause : catchClauses) { // TODO: change scope here - types.add(getType(scope, catchClause.getCatchBody())); + types.add(getType(scope, catchClause.getCatchBody(), true)); } } else { - types.add(getType(scope, finallyBlock.getFinalExpression())); + types.add(getType(scope, finallyBlock.getFinalExpression(), true)); } - types.add(getType(scope, tryBlock)); + types.add(getType(scope, tryBlock, true)); result[0] = commonSupertype(types); } @@ -216,7 +216,7 @@ public class JetTypeChecker { List entries = expression.getEntries(); List types = new ArrayList(); for (JetExpression entry : entries) { - types.add(getType(scope, entry)); + types.add(getType(scope, entry, false)); } // TODO : labels result[0] = JetStandardClasses.getTupleType(types); @@ -247,12 +247,7 @@ public class JetTypeChecker { @Override public void visitBlockExpression(JetBlockExpression expression) { // TODO : this is a stub, consider function literals - List statements = expression.getStatements(); - if (statements.isEmpty()) { - result[0] = JetStandardClasses.getUnitType(); - } else { - result[0] = getType(scope, statements.get(statements.size() - 1)); - } + result[0] = getBlockReturnedType(scope, expression.getStatements()); } @Override @@ -268,6 +263,29 @@ public class JetTypeChecker { return result[0]; } + private Type getBlockReturnedType(@NotNull JetScope outerScope, List block) { + // TODO : this is a stub, consider function literals + if (block.isEmpty()) { + return JetStandardClasses.getUnitType(); + } else { + AccumulatingScope scope = new AccumulatingScope(outerScope); + for (JetElement statement : block) { + // TODO: consider other declarations + if (statement instanceof JetProperty) { + JetProperty property = (JetProperty) statement; + scope.addPropertyDescriptor(ClassDescriptorResolver.resolvePropertyDescriptor(scope, property)); + } + } + JetElement lastElement = block.get(block.size() - 1); + if (lastElement instanceof JetExpression) { + JetExpression expression = (JetExpression) lastElement; + return getType(scope, expression, true); + } + // TODO: functions, classes, etc. + throw new IllegalArgumentException("Last item in the block must be an expression"); + } + } + private void collectAllReturnTypes(JetWhenExpression whenExpression, JetScope scope, List result) { for (JetWhenEntry entry : whenExpression.getEntries()) { JetWhenExpression subWhen = entry.getSubWhen(); @@ -276,7 +294,7 @@ public class JetTypeChecker { } else { JetExpression resultExpression = entry.getExpression(); if (resultExpression != null) { - result.add(getType(scope, resultExpression)); + result.add(getType(scope, resultExpression, true)); } } } diff --git a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java index 7d32f7033da..a6e40bc6149 100644 --- a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java +++ b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java @@ -3,6 +3,7 @@ package org.jetbrains.jet.types; import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.*; import org.jetbrains.jet.lang.types.*; @@ -296,6 +297,13 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { assertType("{Any.(a : Int, b : String) => b}", "{Any.(Int, String) : String}"); } + public void testBlocks() throws Exception { + assertType("if (1) {val a = 1; a} else {null}", "Int?"); + assertType("if (1) {() => val a = 1; a} else {() => null}", "Function0"); + assertType("if (1) {() => val a = 1; a; var b : Boolean; b = true; b} else null", "Function0?"); + assertType("if (1) {() => val a = 1; a; var b = a; b} else null", "Function0?"); + } + public void testImplicitConversions() throws Exception { assertConvertibleTo("1", JetStandardClasses.getByteType()); } @@ -344,13 +352,14 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { private static void assertType(String expression, Type expectedType) { Project project = getProject(); JetExpression jetExpression = JetChangeUtil.createExpression(project, expression); - Type type = JetTypeChecker.INSTANCE.getType(ClassDefinitions.BASIC_SCOPE, jetExpression); + Type type = JetTypeChecker.INSTANCE.getType(ClassDefinitions.BASIC_SCOPE, jetExpression, false); assertTrue(type + " != " + expectedType, JetTypeChecker.INSTANCE.equalTypes(type, expectedType)); } - private void assertType(String contextType, String expression, String expectedType) { + private static void assertType(String contextType, String expression, String expectedType) { final Type thisType = makeType(contextType); JetScope scope = new JetScopeAdapter(ClassDefinitions.BASIC_SCOPE) { + @NotNull @Override public Type getThisType() { return thisType; @@ -366,7 +375,7 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { private static void assertType(JetScope scope, String expression, String expectedTypeStr) { Project project = getProject(); JetExpression jetExpression = JetChangeUtil.createExpression(project, expression); - Type type = JetTypeChecker.INSTANCE.getType(scope, jetExpression); + Type type = JetTypeChecker.INSTANCE.getType(scope, jetExpression, false); Type expectedType = makeType(expectedTypeStr); assertTrue(type + " != " + expectedType, JetTypeChecker.INSTANCE.equalTypes(type, expectedType)); } @@ -392,32 +401,9 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { "open class Derived_outT : Base_outT", }; - public static JetScope BASIC_SCOPE = new JetScopeImpl() { + public static JetScope BASIC_SCOPE = new JetScopeAdapter(JetStandardClasses.STANDARD_CLASSES) { @Override public ClassDescriptor getClass(String name) { - if ("Int".equals(name)) { - return JetStandardClasses.getInt(); - } else if ("Boolean".equals(name)) { - return JetStandardClasses.getBoolean(); - } else if ("Byte".equals(name)) { - return JetStandardClasses.getByte(); - } else if ("Char".equals(name)) { - return JetStandardClasses.getChar(); - } else if ("Short".equals(name)) { - return JetStandardClasses.getShort(); - } else if ("Long".equals(name)) { - return JetStandardClasses.getLong(); - } else if ("Float".equals(name)) { - return JetStandardClasses.getFloat(); - } else if ("Double".equals(name)) { - return JetStandardClasses.getDouble(); - } else if ("Unit".equals(name)) { - return JetStandardClasses.getTuple(0); - } else if ("Any".equals(name)) { - return JetStandardClasses.getAny(); - } else if ("Nothing".equals(name)) { - return JetStandardClasses.getNothing(); - } if (CLASSES.isEmpty()) { for (String classDeclaration : CLASS_DECLARATIONS) { JetClass classElement = JetChangeUtil.createClass(getProject(), classDeclaration); @@ -429,7 +415,7 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { if (classDescriptor != null) { return classDescriptor; } - return null; + return super.getClass(name); } }; }