1 | package uk.co.zonetora.fj.typecheck; |
2 | |
3 | import java.util.ArrayDeque; |
4 | import java.util.ArrayList; |
5 | import java.util.Deque; |
6 | import java.util.List; |
7 | |
8 | import uk.co.zonetora.fj.ast.analysis.DepthFirstAdapter; |
9 | import uk.co.zonetora.fj.model.ArgumentName; |
10 | import uk.co.zonetora.fj.model.Cast; |
11 | import uk.co.zonetora.fj.model.ClassName; |
12 | import uk.co.zonetora.fj.model.FieldAccess; |
13 | import uk.co.zonetora.fj.model.Method; |
14 | import uk.co.zonetora.fj.model.MethodInvocation; |
15 | import uk.co.zonetora.fj.model.ObjectCreation; |
16 | import uk.co.zonetora.fj.model.Term; |
17 | import uk.co.zonetora.fj.model.Variable; |
18 | import uk.co.zonetora.fj.passes.FJException; |
19 | import uk.co.zonetora.fj.util.Arrow; |
20 | import uk.co.zonetora.fj.util.Maybe; |
21 | import uk.co.zonetora.fj.util.Tuple; |
22 | |
23 | import static uk.co.zonetora.fj.util.ListUtil.*; |
24 | |
25 | public class TypeCheck { |
26 | |
27 | private final Deque<ClassName> typeStack; |
28 | private final Deque<Deque<ClassName>> backupStack; |
29 | |
30 | private final TypeEnvironment typeEnvironment; |
31 | private final ClassTable classTable; |
32 | private boolean stupidWarningTriggered; |
33 | |
34 | public TypeCheck(TypeEnvironment typeEnvironment, ClassTable classTable) { |
35 | this.typeEnvironment = typeEnvironment; |
36 | this.classTable = classTable; |
37 | this.typeStack = new ArrayDeque<ClassName>(); |
38 | this.backupStack = new ArrayDeque<Deque<ClassName>>(); |
39 | |
40 | this.stupidWarningTriggered = false; |
41 | } |
42 | |
43 | public ClassName typeCheck(Term code) throws FJException { |
44 | |
45 | code.visit(this); |
46 | |
47 | return this.typeStack.pop(); |
48 | } |
49 | |
50 | public void appyRule(Cast cast) { |
51 | cast.getCastedTerm().visit(this); |
52 | |
53 | ClassName castedTermType = this.typeStack.pop(); |
54 | |
55 | ClassName typeToCastTo = cast.getTypeToCastTo(); |
56 | |
57 | if(this.classTable.subtype(castedTermType, typeToCastTo)) { |
58 | /* ok */ |
59 | } else if(this.classTable.subtype(typeToCastTo, castedTermType)) { |
60 | /* ok */ |
61 | } else { |
62 | this.stupidWarningTriggered = true; |
63 | } |
64 | |
65 | this.typeStack.push(typeToCastTo); |
66 | |
67 | } |
68 | |
69 | public void appyRule(FieldAccess access) { |
70 | |
71 | access.getPath().visit(this); |
72 | |
73 | ClassName pathType = this.typeStack.pop(); |
74 | |
75 | try { |
76 | ClassName fieldType = this.classTable.lookupField(access.getFieldName(), pathType); |
77 | |
78 | this.typeStack.push(fieldType); |
79 | |
80 | } catch (FJException e) { |
81 | throw new FJTypeCheckException(e); |
82 | } |
83 | |
84 | } |
85 | |
86 | |
87 | public void appyRule(final MethodInvocation invocation) { |
88 | invocation.getPath().visit(this); |
89 | final ClassName pathType = this.typeStack.pop(); |
90 | |
91 | Maybe<Method> m = this.classTable.mType(invocation.getMethodName(), pathType); |
92 | |
93 | m.maybe( |
94 | new Runnable() { |
95 | public void run() { |
96 | throw new FJTypeCheckException("Can't find method in " + pathType); |
97 | } |
98 | }, |
99 | |
100 | new Arrow<Method, Runnable> () { |
101 | public Runnable run(final Method arg) { |
102 | return new Runnable() { |
103 | public void run() { |
104 | |
105 | if(arg.getArguments().size() != invocation.getArguments().size()) { |
106 | throw new FJTypeCheckException("Invoking a method with incorrect number of arguments"); |
107 | } |
108 | |
109 | preserveTypeStack(); |
110 | |
111 | for(Term e : invocation.getArguments() ) { |
112 | e.visit(TypeCheck.this); |
113 | } |
114 | |
115 | List<ClassName> argTypes = restoreTypeStack(); |
116 | |
117 | for(Tuple<ClassName, ClassName> cnPair : zip(argTypes, mapFst(arg.getArguments()))) { |
118 | if(!TypeCheck.this.classTable.subtype(cnPair.getX(), cnPair.getY())) { |
119 | throw new FJTypeCheckException("Invoking a method with an argument that is not a subtype"); |
120 | } |
121 | } |
122 | |
123 | TypeCheck.this.typeStack.push(arg.getReturnType()); |
124 | } |
125 | }; |
126 | } |
127 | }).run(); |
128 | |
129 | } |
130 | |
131 | public void appyRule(ObjectCreation creation) { |
132 | List<ClassName> fieldTypes = mapFst(this.classTable.fields(creation.getNewClassName())); |
133 | List<Term> terms = creation.getArgumentTerms(); |
134 | |
135 | if(fieldTypes.size() != terms.size()) { |
136 | throw new FJTypeCheckException("'new' with wrong number of arguments for the constructor"); |
137 | } |
138 | |
139 | preserveTypeStack(); |
140 | for(Term t : terms) { |
141 | t.visit(this); |
142 | } |
143 | List<ClassName> argTypes = restoreTypeStack(); |
144 | |
145 | for(Tuple<ClassName, ClassName> cnPair : zip(argTypes, fieldTypes)) { |
146 | if(!this.classTable.subtype(cnPair.getX(), cnPair.getY())) { |
147 | throw new FJTypeCheckException("Invoking new with an argument that is not of the right type"); |
148 | } |
149 | } |
150 | |
151 | this.typeStack.push(creation.getNewClassName()); |
152 | } |
153 | |
154 | public void appyRule(Variable variable) { |
155 | ArgumentName arg = variable.getArgumentName(); |
156 | |
157 | ClassName cn = this.typeEnvironment.getBinding(arg); |
158 | if(cn == null) { |
159 | throw new FJTypeCheckException("Variable: " + arg + " is unknown"); |
160 | } |
161 | |
162 | this.typeStack.push(cn); |
163 | } |
164 | |
165 | |
166 | private void preserveTypeStack() { |
167 | this.backupStack.push(new ArrayDeque<ClassName>(this.typeStack)); |
168 | } |
169 | |
170 | private List<ClassName> restoreTypeStack() { |
171 | Deque<ClassName> old = this.backupStack.pop(); |
172 | List<ClassName> current = new ArrayList<ClassName>(this.typeStack); |
173 | this.typeStack.clear(); |
174 | this.typeStack.addAll(old); |
175 | return current; |
176 | } |
177 | |
178 | |
179 | } |