001/* 002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010 011package org.eclipse.golo.compiler; 012 013import gololang.ir.*; 014 015import java.util.Deque; 016import java.util.HashSet; 017import java.util.LinkedList; 018import java.util.Set; 019 020import static org.eclipse.golo.compiler.GoloCompilationException.Problem.Type.*; 021import static gololang.Messages.message; 022import static gololang.Messages.prefixed; 023 024public class LocalReferenceAssignmentAndVerificationVisitor extends AbstractGoloIrVisitor { 025 026 private GoloModule module = null; 027 private final AssignmentCounter assignmentCounter = new AssignmentCounter(); 028 private final Deque<GoloFunction> functionStack = new LinkedList<>(); 029 private final Deque<ReferenceTable> tableStack = new LinkedList<>(); 030 private final Deque<Set<LocalReference>> assignmentStack = new LinkedList<>(); 031 private GoloCompilationException.Builder exceptionBuilder; 032 private final HashSet<LocalReference> uninitializedReferences = new HashSet<>(); 033 034 035 private static class AssignmentCounter { 036 037 private int counter = 0; 038 039 public int next() { 040 return counter++; 041 } 042 043 public void reset() { 044 counter = 0; 045 } 046 } 047 048 LocalReferenceAssignmentAndVerificationVisitor() { } 049 050 LocalReferenceAssignmentAndVerificationVisitor(GoloCompilationException.Builder builder) { 051 this(); 052 setExceptionBuilder(builder); 053 } 054 055 public void setExceptionBuilder(GoloCompilationException.Builder builder) { 056 exceptionBuilder = builder; 057 } 058 059 private GoloCompilationException.Builder getExceptionBuilder() { 060 if (exceptionBuilder == null) { 061 exceptionBuilder = new GoloCompilationException.Builder(module.getPackageAndClass().toString()); 062 } 063 return exceptionBuilder; 064 } 065 066 private void errorMessage(GoloCompilationException.Problem.Type type, GoloElement<?> node, String message) { 067 PositionInSourceCode position = null; 068 if (node != null) { 069 position = node.positionInSourceCode(); 070 } 071 String errorMessage = message + ' ' + ( 072 (position != null || position.isUndefined()) 073 ? message("source_position", position.getStartLine(), position.getStartColumn()) 074 : message("generated_code")) + "."; 075 076 getExceptionBuilder().report(type, node, errorMessage); 077 } 078 079 @Override 080 public void visitModule(GoloModule module) { 081 this.module = module; 082 module.walk(this); 083 } 084 085 @Override 086 public void visitFunction(GoloFunction function) { 087 assignmentCounter.reset(); 088 functionStack.push(function); 089 ReferenceTable table = function.getBlock().getReferenceTable(); 090 for (String parameterName : function.getParameterNames()) { 091 LocalReference reference = table.get(parameterName); 092 uninitializedReferences.remove(reference); 093 if (reference == null) { 094 if (!function.isSynthetic()) { 095 throw new IllegalStateException( 096 prefixed("bug", message("parameter_not_declared", parameterName, function.getName()))); 097 } 098 } else { 099 reference.setIndex(assignmentCounter.next()); 100 } 101 } 102 function.walk(this); 103 functionStack.pop(); 104 } 105 106 @Override 107 public void visitBlock(Block block) { 108 ReferenceTable table = block.getReferenceTable(); 109 extractUninitializedReferences(table); 110 tableStack.push(table); 111 assignmentStack.push(extractAssignedReferences(table)); 112 block.walk(this); 113 assignmentStack.pop(); 114 tableStack.pop(); 115 } 116 117 private void extractUninitializedReferences(ReferenceTable table) { 118 for (LocalReference reference : table.ownedReferences()) { 119 if (reference.getIndex() < 0 && !reference.isModuleState()) { 120 reference.setIndex(assignmentCounter.next()); 121 uninitializedReferences.add(reference); 122 } 123 } 124 } 125 126 private Set<LocalReference> extractAssignedReferences(ReferenceTable table) { 127 HashSet<LocalReference> assigned = new HashSet<>(); 128 if (table == functionStack.peek().getBlock().getReferenceTable()) { 129 for (String param : functionStack.peek().getParameterNames()) { 130 assigned.add(table.get(param)); 131 } 132 } 133 if (!assignmentStack.isEmpty()) { 134 assigned.addAll(assignmentStack.peek()); 135 } 136 return assigned; 137 } 138 139 @Override 140 public void visitFunctionInvocation(FunctionInvocation functionInvocation) { 141 if (!tableStack.isEmpty()) { 142 if (tableStack.peek().hasReferenceFor(functionInvocation.getName())) { 143 if (tableStack.peek().get(functionInvocation.getName()).isModuleState()) { 144 functionInvocation.onModuleState(); 145 } else { 146 functionInvocation.onReference(); 147 } 148 } 149 } 150 functionInvocation.walk(this); 151 } 152 153 @Override 154 public void visitAssignmentStatement(AssignmentStatement assignmentStatement) { 155 LocalReference reference = assignmentStatement.getLocalReference(); 156 Set<LocalReference> assignedReferences = assignmentStack.peek(); 157 if (redeclaringReferenceInBlock(assignmentStatement, reference, assignedReferences)) { 158 errorMessage(REFERENCE_ALREADY_DECLARED_IN_BLOCK, assignmentStatement, 159 message("reference_already_declared", reference.getName())); 160 } else if (assigningConstant(reference, assignedReferences)) { 161 errorMessage(ASSIGN_CONSTANT, assignmentStatement, 162 message("assign_constant", reference.getName())); 163 } 164 bindReference(reference); 165 assignedReferences.add(reference); 166 assignmentStatement.walk(this); 167 if (assignmentStatement.isDeclaring() && !reference.isSynthetic()) { 168 uninitializedReferences.remove(reference); 169 } 170 } 171 172 private void bindReference(LocalReference reference) { 173 ReferenceTable table = tableStack.peek(); 174 if (reference.getIndex() < 0) { 175 if (table.hasReferenceFor(reference.getName())) { 176 reference.setIndex(table.get(reference.getName()).getIndex()); 177 } else if (reference.isSynthetic()) { 178 reference.setIndex(assignmentCounter.next()); 179 table.add(reference); 180 } 181 } 182 } 183 184 private boolean redeclaringReferenceInBlock(AssignmentStatement assignmentStatement, LocalReference reference, Set<LocalReference> assignedReferences) { 185 return !reference.isSynthetic() && assignmentStatement.isDeclaring() && referenceNameExists(reference, assignedReferences); 186 } 187 188 private boolean assigningConstant(LocalReference reference, Set<LocalReference> assignedReferences) { 189 return reference.isConstant() && ( 190 assignedReferences.contains(reference) 191 || (reference.isModuleState() && !functionStack.peek().isModuleInit())); 192 } 193 194 private boolean referenceNameExists(LocalReference reference, Set<LocalReference> referencesInBlock) { 195 for (LocalReference ref : referencesInBlock) { 196 if ((ref != null) && ref.getName().equals(reference.getName())) { 197 return true; 198 } 199 } 200 return false; 201 } 202 203 @Override 204 public void visitReferenceLookup(ReferenceLookup referenceLookup) { 205 ReferenceTable table = tableStack.peek(); 206 if (table == null) { return; } 207 if (!table.hasReferenceFor(referenceLookup.getName())) { 208 errorMessage(UNDECLARED_REFERENCE, referenceLookup, 209 message("undeclared_reference", referenceLookup.getName(), 210 !functionStack.isEmpty() 211 ? message("in_function", functionStack.peek().getName()) 212 : "")); 213 } 214 LocalReference ref = referenceLookup.resolveIn(table); 215 if (isUninitialized(ref)) { 216 errorMessage(UNINITIALIZED_REFERENCE_ACCESS, referenceLookup, 217 message("uninitialized_reference_access", ref.getName())); 218 } 219 } 220 221 private boolean isUninitialized(LocalReference ref) { 222 return ref != null && !ref.isSynthetic() && !ref.isModuleState() && uninitializedReferences.contains(ref); 223 } 224 225 @Override 226 public void visitClosureReference(ClosureReference closureReference) { } 227 228 @Override 229 public void visitLoopBreakFlowStatement(LoopBreakFlowStatement loopBreakFlowStatement) { 230 if (loopBreakFlowStatement.getEnclosingLoop() == null) { 231 errorMessage(BREAK_OR_CONTINUE_OUTSIDE_LOOP, loopBreakFlowStatement, 232 message("break_or_continue_outside_loop")); 233 } 234 } 235 236 @Override 237 public void visitMember(Member member) { } 238}