diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParsing.java b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParsing.java index df88c619293..e452327b0b8 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParsing.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParsing.java @@ -1527,7 +1527,8 @@ public class JetParsing extends AbstractJetParsing { parseAnnotations(REGULAR_ANNOTATIONS_ONLY_WITH_BRACKETS); IElementType lookahead = lookahead(1); - if (at(IDENTIFIER) && lookahead(1) != DOT && lookahead != LT && lookahead != LPAR && at(DYNAMIC_KEYWORD)) { + IElementType lookahead2 = lookahead(2); + if (at(IDENTIFIER) && !(lookahead == DOT && lookahead2 == IDENTIFIER) && lookahead != LT && at(DYNAMIC_KEYWORD)) { PsiBuilder.Marker dynamicType = mark(); advance(); // DYNAMIC_KEYWORD dynamicType.done(DYNAMIC_TYPE); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilder.java b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilder.java index f23a4b0130b..44fa204f3ec 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilder.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilder.java @@ -17,6 +17,8 @@ package org.jetbrains.jet.lang.parsing; import com.intellij.lang.PsiBuilder; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; public interface SemanticWhitespaceAwarePsiBuilder extends PsiBuilder { // TODO: comments go to wrong place when an empty element is created, see IElementType.isLeftBound() @@ -29,4 +31,6 @@ public interface SemanticWhitespaceAwarePsiBuilder extends PsiBuilder { void restoreJoiningComplexTokensState(); void enableJoiningComplexTokens(); void disableJoiningComplexTokens(); + + boolean isWhitespaceOrComment(@NotNull IElementType elementType); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderAdapter.java b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderAdapter.java index b435dcba700..6ed6e8e0ce9 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderAdapter.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderAdapter.java @@ -17,6 +17,8 @@ package org.jetbrains.jet.lang.parsing; import com.intellij.lang.impl.PsiBuilderAdapter; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; public class SemanticWhitespaceAwarePsiBuilderAdapter extends PsiBuilderAdapter implements SemanticWhitespaceAwarePsiBuilder { @@ -62,4 +64,9 @@ public class SemanticWhitespaceAwarePsiBuilderAdapter extends PsiBuilderAdapter public void disableJoiningComplexTokens() { myBuilder.disableJoiningComplexTokens(); } + + @Override + public boolean isWhitespaceOrComment(@NotNull IElementType elementType) { + return myBuilder.isWhitespaceOrComment(elementType); + } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderImpl.java b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderImpl.java index 3e4242d8d48..29e086fbea3 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderImpl.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/SemanticWhitespaceAwarePsiBuilderImpl.java @@ -19,9 +19,12 @@ package org.jetbrains.jet.lang.parsing; import com.intellij.lang.PsiBuilder; import com.intellij.lang.impl.PsiBuilderAdapter; +import com.intellij.lang.impl.PsiBuilderImpl; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lexer.JetTokens; import com.intellij.util.containers.Stack; @@ -35,10 +38,36 @@ public class SemanticWhitespaceAwarePsiBuilderImpl extends PsiBuilderAdapter imp private final Stack newlinesEnabled = new Stack(); + private final PsiBuilderImpl delegateImpl; + public SemanticWhitespaceAwarePsiBuilderImpl(PsiBuilder delegate) { super(delegate); newlinesEnabled.push(true); joinComplexTokens.push(true); + + delegateImpl = findPsiBuilderImpl(delegate); + } + + @Nullable + private static PsiBuilderImpl findPsiBuilderImpl(PsiBuilder builder) { + // This is a hackish workaround for PsiBuilder interface not exposing isWhitespaceOrComment() method + // We have to unwrap all the adapters to find an Impl inside + while (true) { + if (builder instanceof PsiBuilderImpl) { + return (PsiBuilderImpl) builder; + } + if (!(builder instanceof PsiBuilderAdapter)) { + return null; + } + + builder = ((PsiBuilderAdapter) builder).getDelegate(); + } + } + + @Override + public boolean isWhitespaceOrComment(@NotNull IElementType elementType) { + assert delegateImpl != null : "PsiBuilderImpl not found"; + return delegateImpl.whitespaceOrComment(elementType); } @Override diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/TruncatedSemanticWhitespaceAwarePsiBuilder.java b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/TruncatedSemanticWhitespaceAwarePsiBuilder.java index edb3a5bf6d9..9620209af4e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/TruncatedSemanticWhitespaceAwarePsiBuilder.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/TruncatedSemanticWhitespaceAwarePsiBuilder.java @@ -30,8 +30,7 @@ public class TruncatedSemanticWhitespaceAwarePsiBuilder extends SemanticWhitespa @Override public boolean eof() { - if (super.eof()) return true; - return myEOFPosition >= 0 && getCurrentOffset() >= myEOFPosition; + return super.eof() || isOffsetBeyondEof(getCurrentOffset()); } @Override @@ -46,4 +45,35 @@ public class TruncatedSemanticWhitespaceAwarePsiBuilder extends SemanticWhitespa return super.getTokenType(); } + @Override + public IElementType lookAhead(int steps) { + if (eof()) return null; + + int rawLookAheadSteps = rawLookAhead(steps); + if (isOffsetBeyondEof(rawTokenTypeStart(rawLookAheadSteps))) return null; + + return super.rawLookup(rawLookAheadSteps); + } + + private int rawLookAhead(int steps) { + // This code reproduces the behavior of PsiBuilderImpl.lookAhead(), but returns a number of raw steps instead of a token type + // This is required for implementing truncated builder behavior + int cur = 0; + while (steps > 0) { + cur++; + + IElementType rawTokenType = rawLookup(cur); + while (rawTokenType != null && isWhitespaceOrComment(rawTokenType)) { + cur++; + rawTokenType = rawLookup(cur); + } + + steps--; + } + return cur; + } + + private boolean isOffsetBeyondEof(int offsetFromCurrent) { + return myEOFPosition >= 0 && offsetFromCurrent >= myEOFPosition; + } } diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/implicitDynamicReceiver.kt b/compiler/testData/diagnostics/tests/dynamicTypes/implicitDynamicReceiver.kt index 5f0f49930d3..97013124fb5 100644 --- a/compiler/testData/diagnostics/tests/dynamicTypes/implicitDynamicReceiver.kt +++ b/compiler/testData/diagnostics/tests/dynamicTypes/implicitDynamicReceiver.kt @@ -1,7 +1,7 @@ // MODULE[js]: m1 // FILE: k.kt -fun (dynamic).test() { +fun dynamic.test() { val v1 = foo() v1.isDynamic() // to check that anything is resolvable diff --git a/compiler/testData/psi/DynamicReceiver.kt b/compiler/testData/psi/DynamicReceiver.kt new file mode 100644 index 00000000000..94636876923 --- /dev/null +++ b/compiler/testData/psi/DynamicReceiver.kt @@ -0,0 +1,16 @@ +fun dynamic.foo() +fun dynamic?.foo() +val dynamic.foo: Int +val dynamic?.foo: Int + +val foo: dynamic.() -> Unit + +// testing look-ahead with comments and whitespace + +fun dynamic . foo() +fun dynamic + .foo() +fun dynamic// line-comment + .foo() +fun dynamic/* +*/.foo() diff --git a/compiler/testData/psi/DynamicReceiver.txt b/compiler/testData/psi/DynamicReceiver.txt new file mode 100644 index 00000000000..a88ae9fe15d --- /dev/null +++ b/compiler/testData/psi/DynamicReceiver.txt @@ -0,0 +1,140 @@ +JetFile: DynamicReceiver.kt + PACKAGE_DIRECTIVE + + FUN + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + TYPE_REFERENCE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiElement(DOT)('.') + PsiElement(IDENTIFIER)('foo') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') + PsiWhiteSpace('\n') + FUN + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + TYPE_REFERENCE + NULLABLE_TYPE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiElement(QUEST)('?') + PsiElement(DOT)('.') + PsiElement(IDENTIFIER)('foo') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') + PsiWhiteSpace('\n') + PROPERTY + PsiElement(val)('val') + PsiWhiteSpace(' ') + TYPE_REFERENCE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiElement(DOT)('.') + PsiElement(IDENTIFIER)('foo') + PsiElement(COLON)(':') + PsiWhiteSpace(' ') + TYPE_REFERENCE + USER_TYPE + REFERENCE_EXPRESSION + PsiElement(IDENTIFIER)('Int') + PsiWhiteSpace('\n') + PROPERTY + PsiElement(val)('val') + PsiWhiteSpace(' ') + TYPE_REFERENCE + NULLABLE_TYPE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiElement(QUEST)('?') + PsiElement(DOT)('.') + PsiElement(IDENTIFIER)('foo') + PsiElement(COLON)(':') + PsiWhiteSpace(' ') + TYPE_REFERENCE + USER_TYPE + REFERENCE_EXPRESSION + PsiElement(IDENTIFIER)('Int') + PsiWhiteSpace('\n\n') + PROPERTY + PsiElement(val)('val') + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('foo') + PsiElement(COLON)(':') + PsiWhiteSpace(' ') + TYPE_REFERENCE + FUNCTION_TYPE + FUNCTION_TYPE_RECEIVER + TYPE_REFERENCE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiElement(DOT)('.') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') + PsiWhiteSpace(' ') + PsiElement(ARROW)('->') + PsiWhiteSpace(' ') + TYPE_REFERENCE + USER_TYPE + REFERENCE_EXPRESSION + PsiElement(IDENTIFIER)('Unit') + PsiWhiteSpace('\n\n') + PsiComment(EOL_COMMENT)('// testing look-ahead with comments and whitespace') + PsiWhiteSpace('\n\n') + FUN + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + TYPE_REFERENCE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiWhiteSpace(' ') + PsiElement(DOT)('.') + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('foo') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') + PsiWhiteSpace('\n') + FUN + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + TYPE_REFERENCE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiWhiteSpace('\n ') + PsiElement(DOT)('.') + PsiElement(IDENTIFIER)('foo') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') + PsiWhiteSpace('\n') + FUN + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + TYPE_REFERENCE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiComment(EOL_COMMENT)('// line-comment') + PsiWhiteSpace('\n ') + PsiElement(DOT)('.') + PsiElement(IDENTIFIER)('foo') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') + PsiWhiteSpace('\n') + FUN + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + TYPE_REFERENCE + DYNAMIC_TYPE + PsiElement(dynamic)('dynamic') + PsiComment(BLOCK_COMMENT)('/*\n*/') + PsiElement(DOT)('.') + PsiElement(IDENTIFIER)('foo') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java b/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java index 49406b1faef..965dd6f06c6 100644 --- a/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java +++ b/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java @@ -54,7 +54,7 @@ public class JetCodeConformanceTest extends TestCase { for (File sourceFile : FileUtil.findFilesByMask(JAVA_FILE_PATTERN, new File("compiler/frontend/src/org/jetbrains/jet/lang/parsing"))) { String source = FileUtil.loadFile(sourceFile, true); - Pattern atPattern = Pattern.compile("assert.*?[^_]at.*?$", Pattern.MULTILINE); + Pattern atPattern = Pattern.compile("assert.*?\\b[^_]at.*?$", Pattern.MULTILINE); Matcher matcher = atPattern.matcher(source); if (matcher.find()) { diff --git a/compiler/tests/org/jetbrains/jet/parsing/JetParsingTestGenerated.java b/compiler/tests/org/jetbrains/jet/parsing/JetParsingTestGenerated.java index cefe8e5e19a..90031437fb8 100644 --- a/compiler/tests/org/jetbrains/jet/parsing/JetParsingTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/parsing/JetParsingTestGenerated.java @@ -166,6 +166,12 @@ public class JetParsingTestGenerated extends AbstractJetParsingTest { doParsingTest(fileName); } + @TestMetadata("DynamicReceiver.kt") + public void testDynamicReceiver() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/psi/DynamicReceiver.kt"); + doParsingTest(fileName); + } + @TestMetadata("DynamicSoftKeyword.kt") public void testDynamicSoftKeyword() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/psi/DynamicSoftKeyword.kt"); diff --git a/spec-docs/dynamic-types.md b/spec-docs/dynamic-types.md index 0613110cb8e..ab880b012fa 100644 --- a/spec-docs/dynamic-types.md +++ b/spec-docs/dynamic-types.md @@ -45,7 +45,7 @@ type "dynamic" is a *soft keyword*: - if it occurs in a non-type context, it's an identifier -- in a type context, when followed by a dot or an angle bracket `<`, it's an identifier +- in a type context, when followed by a dot (except for a dot that separates a receiver type from a function/property name) or an angle bracket `<`, it's an identifier ## Representation