Skip to content

Commit 0731a79

Browse files
committed
Merge branch 'issue_9'
# Conflicts: # gen/com/eiffel/parser/EiffelParser.java # gen/com/eiffel/psi/EiffelTypes.java # gen/com/eiffel/psi/EiffelVisitor.java
2 parents bf41e1f + 725a6db commit 0731a79

File tree

8 files changed

+274
-18
lines changed

8 files changed

+274
-18
lines changed

resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<annotator language="Eiffel" implementationClass="com.eiffel.annotators.EiffelCurrentFeatureReferenceAnnotator"/>
6262
<annotator language="Eiffel" implementationClass="com.eiffel.annotators.EiffelCreationFeatureNotNoneAnnotator"/>
6363
<annotator language="Eiffel" implementationClass="com.eiffel.annotators.EiffelCreationFeatureNotVoidAnnotator"/>
64+
<annotator language="Eiffel" implementationClass="com.eiffel.annotators.EiffelAssignmentConformanceAnnotator"/>
6465
<lang.formatter language="Eiffel" implementationClass="com.eiffel.formatting.EiffelFormattingModelBuilder"/>
6566

6667
<toolWindow id="GOBO run window" icon="/com/eiffel/icons/ecf/e.png" anchor="bottom" factoryClass="com.eiffel.windows.run.RunToolWindowFactory"/>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.eiffel.annotators;
2+
3+
import com.eiffel.psi.*;
4+
import com.intellij.lang.annotation.AnnotationHolder;
5+
import com.intellij.lang.annotation.Annotator;
6+
import com.intellij.psi.PsiElement;
7+
import org.jetbrains.annotations.NotNull;
8+
9+
public class EiffelAssignmentConformanceAnnotator implements Annotator {
10+
@Override
11+
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
12+
if (element instanceof EiffelAssignment) {
13+
EiffelVariable variable = ((EiffelAssignment) element).getVariable();
14+
EiffelExpression expression = ((EiffelAssignment) element).getExpression();
15+
16+
String variableTypeString = EiffelTypeResolutionUtil.getTypeString(variable.getProject(), variable);
17+
EiffelClassDeclaration variableType = EiffelClassUtil.findClassDeclaration(
18+
variable.getProject(),
19+
variableTypeString
20+
);
21+
22+
String expressionTypeString = EiffelTypeResolutionUtil.getTypeString(expression.getProject(), expression);
23+
EiffelClassDeclaration expressionType = EiffelClassUtil.findClassDeclaration(
24+
expression.getProject(),
25+
expressionTypeString
26+
);
27+
28+
if (expressionType == null || variableType == null) return;
29+
30+
if (!expressionType.isAncestorOf(variableType) && !expressionType.equals(variableType)) {
31+
holder.createErrorAnnotation(
32+
expression,
33+
"Type {" + expressionTypeString + "} of expression does not conform to type {" + variableTypeString + "} of assignment target"
34+
);
35+
}
36+
}
37+
}
38+
}

src/com/eiffel/parser/Eiffel.bnf

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ class_declaration ::=
137137
getAllNewFeaturesSubset
138138
getCreationProcedures
139139
getNewFeature
140+
141+
hasAncestor
142+
isAncestorOf
140143
]
141144
stubClass="com.eiffel.psi.stubs.EiffelClassDeclarationStub"
142145
}
@@ -187,6 +190,7 @@ new_feature ::= ['frozen'] extended_feature_name
187190
methods = [
188191
getName
189192
getTypeString
193+
getType
190194
getSerializedFormalArguments
191195
getFeatureDeclaration
192196
getFeatureClause
@@ -251,6 +255,7 @@ entity_identifier ::= IDENTIFIER
251255
{
252256
methods=[
253257
getTypeString
258+
getType
254259
]
255260
implements="com.eiffel.psi.EiffelTypedElement"
256261
}
@@ -317,9 +322,15 @@ parent_qualification ::= '{' class_name '}'
317322
redefine ::= 'redefine' feature_list
318323
undefine ::= 'undefine' feature_list
319324

320-
type ::= 'none' | class_or_tuple_type | formal_generic_name | anchored
325+
type ::= 'none' | class_or_tuple_type | formal_generic_name | anchored {
326+
methods=[
327+
conformsTo
328+
getString
329+
getUngenerified
330+
]
331+
}
321332
private class_or_tuple_type ::= tuple_type | class_type
322-
private class_type ::= ['detachable' | 'attached'] ['separate'] [attachment_mark] class_name [actual_generics]
333+
private class_type ::= ['detachable' | 'attached'] ['separate'] [attachment_mark] class_name [actual_generics] { extends=type }
323334
attachment_mark ::= '?' | '!'
324335
private anchored ::= ['detachable' | 'attached'] ['separate'] [attachment_mark] 'like' anchor
325336
private anchor ::= expression
@@ -340,7 +351,7 @@ multiple_constraint ::= '{' constraint_list '}'
340351
private constraint_list ::= single_constraint (',' single_constraint)*
341352
constraint_creators ::= 'create' feature_list 'end'
342353

343-
tuple_type ::= ['detachable' | 'attached'] ['separate'] 'tuple' [tuple_parameter_list]
354+
private tuple_type ::= ['detachable' | 'attached'] ['separate'] 'tuple' [tuple_parameter_list] { extends=type }
344355
private tuple_parameter_list ::= '[' tuple_parameters ']'
345356
private tuple_parameters ::= entity_declaration_list | type_list
346357

@@ -422,15 +433,15 @@ assignment ::= variable ':=' expression
422433

423434
assigner_call ::= expression ':=' expression
424435

425-
private call ::= object_call | non_object_call | non_object_parenthesized_call | casting_call
426-
object_call ::= (target_atomic '.' call) | (target_atomic bracketed_actuals '.' call) | (target_atomic bracketed_actuals) | unqualified_call
436+
call ::= object_call | non_object_call | non_object_parenthesized_call | casting_call
437+
object_call ::= (target_atomic '.' call) | (target_atomic bracketed_actuals '.' call) | (target_atomic bracketed_actuals) | unqualified_call { extends=call }
427438
unqualified_call ::= (feature_name [actuals]) | ('current' bracketed_actuals)
428439
private target_atomic ::= casting_call | unqualified_call | local | read_only | non_object_call | parenthesized
429440
private target ::= target_atomic ['.' call]
430-
non_object_call ::= '{' type '}' '.' unqualified_call
431-
non_object_parenthesized_call ::= '(' '{' type '}' ')' DOT unqualified_call
441+
non_object_call ::= '{' type '}' '.' unqualified_call { extends=call }
442+
non_object_parenthesized_call ::= '(' '{' type '}' ')' DOT unqualified_call { extends=call }
432443

433-
casting_call ::= '{' type '}' actuals
444+
casting_call ::= '{' type '}' actuals { extends=call }
434445

435446
actuals ::= parenthesized_actuals | bracketed_actuals
436447
private parenthesized_actuals ::= '(' actual_list ')'
@@ -510,7 +521,7 @@ private character_constant ::= CHARACTER_LITERAL
510521
private boolean_constant ::= TRUE_KEYWORD | FALSE_KEYWORD
511522
private real_constant ::= REAL_LITERAL
512523

513-
manifest_string ::= STRING_LITERAL
524+
private manifest_string ::= STRING_LITERAL
514525
array_literal ::= '<<' expression_list '>>'
515526

516527
ext ::= EXTERNAL_KEYWORD external_language [external_name]

src/com/eiffel/psi/EiffelTypeResolutionUtil.java

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,105 @@ public static String getTypeString(Project project, @NotNull PsiElement element,
2626
if (!(element instanceof EiffelObjectCall)) {
2727
return null;
2828
}
29-
return getTypeStringForObjectCall(project, (EiffelObjectCall) element);
29+
return getTypeStringForTailObjectCall(project, (EiffelObjectCall) element);
30+
} else {
31+
if (element instanceof EiffelExpression) {
32+
return getTypeStringForExpression(project, (EiffelExpression) element);
33+
}
34+
if (element instanceof EiffelVariable) {
35+
return getTypeStringForVariable(project, (EiffelVariable) element);
36+
}
37+
}
38+
return null;
39+
}
40+
41+
@Nullable
42+
private static String getTypeStringForVariable(Project project, EiffelVariable variable) {
43+
if (variable.getText().toLowerCase().equals("Current")) {
44+
return null; // i don't really know how to manage these situations well
45+
} else {
46+
EiffelClassDeclaration context = EiffelClassUtil.findClassDeclaration(variable);
47+
if (context == null) return null;
48+
EiffelNewFeature feature = context.getNewFeature(variable.getText());
49+
if (feature == null) return null;
50+
return feature.getTypeString();
51+
}
52+
}
53+
54+
@Nullable
55+
private static String getTypeStringForExpression(Project project, EiffelExpression expr) {
56+
if (expr instanceof EiffelBasicExpression) {
57+
/*
58+
array_literal
59+
object_test
60+
creation_expression
61+
call
62+
manifest_constant
63+
parenthesized
64+
local
65+
read_only
66+
boolean_loop
67+
precursor
68+
manifest_tuple
69+
old
70+
bracket_expression
71+
*/
72+
EiffelBasicExpression bExpr = (EiffelBasicExpression) expr;
73+
if (bExpr.getObjectTest() != null) return "BOOLEAN";
74+
if (bExpr.getCall() != null) return getTypeStringForCall(project, bExpr.getCall());
75+
if (bExpr.getManifestConstant() != null) return getTypeStringForLiteral(project, bExpr.getManifestConstant());
3076
}
3177
return null;
3278
}
3379

3480
@Nullable
35-
private static String getTypeStringForObjectCall(Project project, @NotNull EiffelObjectCall objectCall) {
81+
private static String getTypeStringForCall(Project project, @NotNull EiffelCall call) {
82+
if (call instanceof EiffelObjectCall) {
83+
EiffelClassDeclaration context = EiffelClassUtil.findClassDeclaration(call);
84+
if (context == null) return null;
85+
String contextTypeString = context.getName();
86+
PsiElement current = call;
87+
while (current.getLastChild() instanceof EiffelObjectCall) {
88+
EiffelClassDeclaration contextClass = EiffelClassUtil.findClassDeclaration(project, contextTypeString);
89+
PsiElement unqualifiedCall = current.getFirstChild();
90+
if (unqualifiedCall instanceof EiffelUnqualifiedCall && contextClass != null) {
91+
EiffelNewFeature feature = contextClass.getNewFeature(unqualifiedCall.getText());
92+
if (feature != null) {
93+
contextTypeString = feature.getTypeString();
94+
}
95+
}
96+
current = current.getLastChild();
97+
}
98+
EiffelClassDeclaration contextClass = EiffelClassUtil.findClassDeclaration(project, contextTypeString);
99+
PsiElement unqualifiedCall = current.getFirstChild();
100+
if (unqualifiedCall instanceof EiffelUnqualifiedCall && contextClass != null) {
101+
EiffelNewFeature feature = contextClass.getNewFeature(unqualifiedCall.getText());
102+
if (feature != null) {
103+
contextTypeString = feature.getTypeString();
104+
}
105+
}
106+
return contextTypeString;
107+
}
108+
return null;
109+
}
110+
111+
@NotNull
112+
private static String getTypeStringForLiteral(Project project, @NotNull EiffelManifestConstant literal) {
113+
// manifest_constant goes directly into tokens, therefore using ASTNode here
114+
ASTNode literalNode = literal.getNode();
115+
// last child node is supposed to be the value, except for some rare cases, which are non standard-compliant
116+
ASTNode lastChildNode = literalNode.getLastChildNode();
117+
if (lastChildNode.getElementType().equals(EiffelTypes.INTEGER_LITERAL)) return "INTEGER_32";
118+
if (lastChildNode.getElementType().equals(EiffelTypes.REAL_LITERAL)) return "REAL_32";
119+
if (lastChildNode.getElementType().equals(EiffelTypes.STRING_LITERAL)) return "STRING_8";
120+
if (lastChildNode.getElementType().equals(EiffelTypes.CHARACTER_LITERAL)) return "CHARACTER_8";
121+
if (TokenSet.create(EiffelTypes.TRUE_KEYWORD, EiffelTypes.FALSE_KEYWORD).contains(lastChildNode.getElementType()))
122+
return "BOOLEAN";
123+
return "ANY";
124+
}
125+
126+
@Nullable
127+
private static String getTypeStringForTailObjectCall(Project project, @NotNull EiffelObjectCall objectCall) {
36128
EiffelClassDeclaration currentClassDeclaration = EiffelClassUtil.findClassDeclaration(objectCall);
37129
if (currentClassDeclaration == null) return null;
38130

src/com/eiffel/psi/EiffelTypedElement.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44

55
public interface EiffelTypedElement extends PsiElement {
66
String getTypeString();
7+
EiffelType getType();
78
}

src/com/eiffel/psi/impl/EiffelPsiImplUtil.java

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package com.eiffel.psi.impl;
22

33
import com.eiffel.psi.*;
4+
import com.eiffel.util.StreamUtil;
45
import com.intellij.lang.ASTNode;
6+
import com.intellij.openapi.project.Project;
57
import com.intellij.psi.PsiElement;
6-
import com.intellij.psi.TokenType;
78
import com.intellij.psi.tree.TokenSet;
89
import com.intellij.util.containers.ContainerUtil;
10+
import javafx.util.Pair;
911
import org.jetbrains.annotations.NotNull;
1012
import org.jetbrains.annotations.Nullable;
1113

1214
import java.util.*;
1315
import java.util.concurrent.LinkedBlockingQueue;
16+
import java.util.stream.Stream;
1417

1518
public class EiffelPsiImplUtil {
1619
@Nullable
@@ -189,6 +192,14 @@ public static List<EiffelClassDeclaration> getParents(EiffelClassDeclaration cla
189192
);
190193
}
191194

195+
public static boolean hasAncestor(EiffelClassDeclaration child, EiffelClassDeclaration candidate) {
196+
return child.getParents().contains(candidate);
197+
}
198+
199+
public static boolean isAncestorOf(EiffelClassDeclaration ancestor, EiffelClassDeclaration candidiate) {
200+
return candidiate.getParents().contains(ancestor);
201+
}
202+
192203
@NotNull
193204
public static Map<EiffelClassDeclaration, Integer> getParentsWithDepth(EiffelClassDeclaration classDeclaration) {
194205
Map<EiffelClassDeclaration, Integer> result = new HashMap<>();
@@ -248,6 +259,11 @@ public static String getTypeString(EiffelNewFeature newFeature) {
248259
return null;
249260
}
250261

262+
@Nullable
263+
public static EiffelType getType(EiffelNewFeature newFeature) {
264+
return null;
265+
}
266+
251267
@Nullable
252268
public static String getSerializedFormalArguments(EiffelNewFeature newFeature) {
253269
if (newFeature.getStub() != null) return newFeature.getStub().getSerializedFormalArguments();
@@ -360,18 +376,21 @@ public static EiffelEntityIdentifier getFormalArgumentIdentifier(EiffelFeatureDe
360376

361377
@Nullable
362378
public static String getTypeString(EiffelEntityIdentifier identifier) {
379+
EiffelType type = identifier.getType();
380+
return type == null ? null : type.getUngenerified();
381+
}
382+
383+
@Nullable
384+
public static EiffelType getType(EiffelEntityIdentifier identifier) {
363385
PsiElement parent = identifier.getParent();
364386
if (parent instanceof EiffelEntityDeclarationGroup) {
365387
EiffelEntityDeclarationGroup group = (EiffelEntityDeclarationGroup) parent;
366-
EiffelType type = group.getType();
367-
if (type == null) return null;
368-
EiffelClassName className = type.getClassName();
369-
if (className == null) return null;
370-
return EiffelClassUtil.formalizeName(className.getText());
388+
return group.getType();
371389
}
372390
return null;
373391
}
374392

393+
375394
@NotNull
376395
public static List<EiffelNewFeature> getCreationProcedures(EiffelClassDeclaration classDeclaration) {
377396
List<EiffelNewFeature> result = new ArrayList<>();
@@ -399,4 +418,45 @@ public static String getCommentDoc(EiffelNewFeature newFeature) {
399418
if (fd == null) return null;
400419
return fd.getCommentDoc();
401420
}
421+
422+
@Nullable
423+
public static String getString(EiffelType type) {
424+
return type.getText();
425+
}
426+
427+
public static boolean conformsTo(EiffelType source, EiffelType destination) {
428+
final Project project = source.getProject();
429+
430+
EiffelActualGenerics destinationGenerics = destination.getActualGenerics();
431+
EiffelActualGenerics sourceGenerics = source.getActualGenerics();
432+
433+
if (source.getClassName() == null || destination.getClassName() == null) {
434+
return true; // TODO: implement non-class types, e.g. tuples
435+
}
436+
EiffelClassDeclaration sourceDeclaration = EiffelClassUtil.findClassDeclaration(project, source.getClassName().getText());
437+
EiffelClassDeclaration destinationDeclaration =
438+
EiffelClassUtil.findClassDeclaration(project, destination.getClassName().getText());
439+
440+
if (destinationGenerics == null && sourceGenerics == null) {
441+
if (sourceDeclaration == null || destinationDeclaration == null) return true;
442+
return sourceDeclaration.hasAncestor(destinationDeclaration);
443+
} else if (destinationGenerics == null || sourceGenerics == null) {
444+
return false;
445+
} else {
446+
List<EiffelType> dstTypeList = destinationGenerics.getTypeList();
447+
List<EiffelType> srcTypeList = sourceGenerics.getTypeList();
448+
if (dstTypeList.size() != srcTypeList.size()) return false;
449+
450+
return StreamUtil.zipIntoPairs(srcTypeList.stream(), dstTypeList.stream()).allMatch(
451+
p -> p.getKey().conformsTo(p.getValue())
452+
);
453+
}
454+
}
455+
456+
@Nullable
457+
public static String getUngenerified(EiffelType type) {
458+
EiffelClassName className = type.getClassName();
459+
if (className == null) return null;
460+
return EiffelClassUtil.formalizeName(className.getText());
461+
}
402462
}

src/com/eiffel/psi/stubs/impl/EiffelClassDeclarationStubIndex.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
public class EiffelClassDeclarationStubIndex extends StringStubIndexExtension {
99
@Override
1010
public int getVersion() {
11-
return 28;
11+
return 30;
1212
}
1313

1414
@NotNull

0 commit comments

Comments
 (0)