From 8cbc495c575c7859570ccc60977f32e619140957 Mon Sep 17 00:00:00 2001 From: bill Date: Mon, 22 Mar 2021 06:24:16 +0800 Subject: [PATCH] Type Checker finished Bug fixes (passed 59/59 tests) --- .../java/chocopy/pa2/DeclarationAnalyzer.java | 330 ++++++++------ .../java/chocopy/pa2/StudentAnalysis.java | 70 ++- src/main/java/chocopy/pa2/TypeChecker.java | 422 +++++++++++++++++- 3 files changed, 682 insertions(+), 140 deletions(-) diff --git a/src/main/java/chocopy/pa2/DeclarationAnalyzer.java b/src/main/java/chocopy/pa2/DeclarationAnalyzer.java index d2e5fc3..a7f4e39 100644 --- a/src/main/java/chocopy/pa2/DeclarationAnalyzer.java +++ b/src/main/java/chocopy/pa2/DeclarationAnalyzer.java @@ -12,158 +12,195 @@ public class DeclarationAnalyzer extends AbstractNodeAnalyzer /** Current symbol table. Changes with new declarative region. */ private SymbolTable sym = new SymbolTable<>(); /** Global symbol table. */ - private final SymbolTable globals = sym; + private final SymbolTable globals; /** Receiver for semantic error messages. */ + + private final TypeChecker typeChecker; private final Errors errors; + private final boolean firstPass; + // In the first pass declanalyzer will create the global symtable + // In the second pass, typeAnalyzer will call declanalyzer to + // analyze local vars/func/class defs and create sub-scope symtable. private ClassVType current_class=null; /** A new declaration analyzer sending errors to ERRORS0. */ public DeclarationAnalyzer(Errors errors0) { + firstPass = true; errors = errors0; - ClassVType cvt = new ClassVType("object"); - FuncValueType init = new FuncValueType(Type.OBJECT_TYPE); + globals = sym; + ClassVType cvt = new ClassVType("object"), obj = cvt; + FuncType init = new FuncType(Type.OBJECT_TYPE); init.parameters.add(Type.OBJECT_TYPE); SymbolTable cvt_scope=new SymbolTable<>(sym); cvt_scope.put("init",init); cvt.scope=cvt_scope; sym.put("object", cvt); cvt = new ClassVType("int"); - init = new FuncValueType(Type.INT_TYPE); + cvt.super_class = obj; + init = new FuncType(Type.INT_TYPE); init.parameters.add(Type.INT_TYPE); cvt_scope=new SymbolTable<>(sym); cvt_scope.put("init",init); cvt.scope=cvt_scope; sym.put("int", cvt); cvt = new ClassVType("str"); - init = new FuncValueType(Type.STR_TYPE); + cvt.super_class = obj; + init = new FuncType(Type.STR_TYPE); init.parameters.add(Type.STR_TYPE); cvt_scope=new SymbolTable<>(sym); cvt_scope.put("init",init); cvt.scope=cvt_scope; sym.put("str", cvt); cvt = new ClassVType("bool"); - init = new FuncValueType(Type.BOOL_TYPE); + cvt.super_class = obj; + init = new FuncType(Type.BOOL_TYPE); init.parameters.add(Type.BOOL_TYPE); cvt_scope=new SymbolTable<>(sym); cvt_scope.put("init",init); cvt.scope=cvt_scope; sym.put("bool", cvt); + + cvt = new ClassVType(""); + cvt.super_class = obj; + sym.put("", cvt); + + ArrayList param = new ArrayList(); + param.add(Type.OBJECT_TYPE); + sym.put("print", new FuncType(param, Type.NONE_TYPE)); + + param = new ArrayList(); + param.add(Type.OBJECT_TYPE); + sym.put("len", new FuncType(param, Type.INT_TYPE)); + + sym.put("input", new FuncType(new ArrayList<>(), Type.STR_TYPE)); + typeChecker = new TypeChecker(globals, errors); } + public DeclarationAnalyzer(Errors errors0, TypeChecker typeChecker, SymbolTable globals){ + firstPass = false; + this.typeChecker = typeChecker; + errors = errors0; + this.globals = globals; + } public SymbolTable getGlobals() { return globals; } - - @Override - public Type analyze(Program program) - { - for (Declaration decl : program.declarations) - { - Identifier id = decl.getIdentifier(); - String name = id.name; - Type type = decl.dispatch(this); - - if (type == null) + private void putSymChecked(Node node, String name, Type ty){ + if (ty == null) { - continue; + return; } - if (sym.declares(name)) + if (globals.get(name)!= null && !(ty instanceof ClassVType) && globals.get(name) instanceof ClassVType) //class names are only in global scope { - errors.semError( - id, "Duplicate declaration of identifier in same " + "scope: %s", name); - } - else if (sym.get(name)!= null && sym.get(name) instanceof ClassVType) + errors.semError(node, "Cannot shadow class name: %s", name); + } + else if (sym.declares(name)) { errors.semError( - id, "Cannot shadow class name: %s", name); - continue; + node, "Duplicate declaration of identifier in same scope: %s", name); + } + else { - sym.put(name, type); + sym.put(name, ty); } + } + @Override + public Type analyze(Program program) + { + for (Declaration decl : program.declarations) + { + Identifier id = decl.getIdentifier(); + String name = id.name; + Type type = decl.dispatch(this); + + } // Check for return statements at top for (Stmt stmt : program.statements) { if (stmt instanceof ReturnStmt) - errors.semError( - stmt, "Return statement cannot appear at the top level"); + errors.semError( + stmt, "Return statement cannot appear at the top level"); } return null; } + @Override public Type analyze(FuncDef node) { - FuncValueType current_func=new FuncValueType((ValueType) node.returnType.dispatch(this)); - SymbolTable current_scope=new SymbolTable<>(sym); - sym=current_scope; - for (TypedVar param : node.params) - { - - if (sym.declares(param.identifier.name)) + Type fTy = globals.get(node.name.name); + FuncType current_func=null; + + if(!(fTy instanceof FuncType)){ + if(fTy == null) { - errors.semError( - node.name, "Duplicate declaration of identifier in same " + "scope: %s", param.identifier.name); - continue; + current_func = new FuncType(new ArrayList(), + ValueType.annotationToValueType(node.returnType)); + + for (TypedVar param : node.params) + { + Type p = ValueType.annotationToValueType(param.type); + current_func.parameters.add((ValueType)p); + } + sym.put(node.name.name, current_func); + if(!firstPass) + { + SymbolTable parent = sym.getParent(); + if(parent!=null && parent != globals){ + parent.put(node.name.name, current_func); + } + } } - else if (sym.get(param.identifier.name)!= null && sym.get(param.identifier.name) instanceof ClassVType) - { + else if(fTy instanceof ClassVType) + errors.semError(node.name, "Cannot shadow class name: %s", node.name.name); + else errors.semError( - param.identifier, "Cannot shadow class name: %s", param.identifier.name); - continue; - } + node.name, "Duplicate declaration of identifier in same scope: %s", node.name.name); - Type p = param.dispatch(this); - current_func.parameters.add((ValueType)p); - sym.put(param.identifier.name, p); } + else if(firstPass || sym.declares(node.name.name)) + errors.semError( + node.name, "Duplicate declaration of identifier in same scope: %s", node.name.name); + + if(!firstPass){ + ValueType returnType = ValueType.annotationToValueType(node.returnType); + if(returnType!=null && !returnType.isSpecialType() && !(globals.get(returnType.className()) instanceof ClassVType)) + errors.semError( + node.returnType, "Invalid type annotation; there is no class named: %s", returnType.className()); - for (Declaration decl : node.declarations) - { - Identifier id = decl.getIdentifier(); - String name = id.name; - Type type = decl.dispatch(this); - - if (type == null) + for(TypedVar param : node.params) { - continue; - } + ValueType pTy = ValueType.annotationToValueType(param.type); + + if(!(pTy.isListType() && !pTy.elementType().equals(Type.EMPTY_TYPE))&&!pTy.isSpecialType() && !(globals.get(pTy.className()) instanceof ClassVType)) + errors.semError(param.type, "Invalid type annotation; there is no class named: %s", pTy.className()); - if (sym.declares(name)) - { - errors.semError( - id, "Duplicate declaration of identifier in same " + "scope: %s", name); - continue; - } - else if (sym.get(name)!= null && sym.get(name) instanceof ClassVType) - { - errors.semError(id, "Cannot shadow class name: %s", name); - continue; - } - else - { - sym.put(name, type); + putSymChecked(param.identifier, param.identifier.name, pTy); } + ArrayList varDefs = new ArrayList<>(), otherDefs = new ArrayList<>(); + for (Declaration decl : node.declarations) + if(decl instanceof VarDef || decl instanceof GlobalDecl || decl instanceof NonLocalDecl) + varDefs.add(decl); + else + otherDefs.add(decl); + for (Declaration decl : varDefs) + if(decl instanceof VarDef) + decl.dispatch(this); + else + decl.dispatch(typeChecker); + for(Declaration decl : otherDefs) + if(decl instanceof FuncDef) + decl.dispatch(typeChecker); + else + decl.dispatch(this); } - /*for(String s:sym.getDeclaredSymbols()) - System.out.println(" "+s);*/ - sym = sym.getParent(); return current_func; } - @Override - public Type analyze(GlobalDecl node) - { - if (globals.declares(node.variable.name)==false) - { - errors.semError( - node.variable, "Not a global variable: %s", node.variable.name); - return null; - } - return globals.get(node.variable.name); - } - public boolean compare_functions(FuncValueType fun1, FuncValueType fun2) { + + public boolean compare_functions(FuncType fun1, FuncType fun2) { if (fun1.returnType.equals(fun2.returnType)==false) return false; if (fun1.parameters.size() != fun2.parameters.size()) @@ -173,6 +210,7 @@ public class DeclarationAnalyzer extends AbstractNodeAnalyzer return false; return true; } + @Override public Type analyze(ClassDef node) { @@ -181,99 +219,123 @@ public class DeclarationAnalyzer extends AbstractNodeAnalyzer sym=current_scope; current_class=cvt; Type super_class = sym.get(node.superClass.name); - cvt.super_class = (ClassVType)super_class; + if(super_class instanceof ClassVType) + cvt.super_class = (ClassVType)super_class;//new ClassVType(super_class.className()); + SymbolTable super_scope=null; Set super_syms=null; if (super_class == null) { errors.semError( - node.name, "Super-class not defined: %s", node.superClass.name); + node.superClass, "Super-class not defined: %s", node.superClass.name); } else if ((super_class instanceof ClassVType)==false) { errors.semError( - node.name, "Super-class must be a class: %s", node.superClass.name); + node.superClass, "Super-class must be a class: %s", node.superClass.name); } else if (node.superClass.name.equals("int") || node.superClass.name.equals("bool") || node.superClass.name.equals("str")) { errors.semError( - node.name, "Cannot extend special class: %s", node.superClass.name); + node.superClass, "Cannot extend special class: %s", node.superClass.name); } else { super_scope = cvt.super_class.scope; - super_syms = super_scope.getDeclaredSymbols(); + if(cvt.super_class.scope != null) + super_syms = super_scope.getDeclaredSymbols(); + else + super_syms = new HashSet(); + HashSet curr_syms = new HashSet<>(); for (Declaration decl : node.declarations) { Identifier id = decl.getIdentifier(); String name = id.name; - Type type = decl.dispatch(this); - if (type == null) - continue; - - if (sym.declares(name)) - { - errors.semError( - id, "Duplicate declaration of identifier in same " + "scope: %s", name); - } - if (type instanceof ClassVType || type instanceof ListValueType) - {//Check for attribute type - if (super_syms.contains(name)) - {//Check for redefined superclass attribute - errors.semError( - id, "Cannot re-define attribute: %s", name); - } - else - sym.put(name, type); - } - else if(type instanceof FuncValueType) + Type type = null;//decl.dispatch(this); + type = decl.dispatch(this); + if(type instanceof FuncType) {//For function declarations - List params = ((FuncValueType)type).parameters; - if(params.size() < 1 || (params.get(0) instanceof ClassVType==false) || ((ClassVType)params.get(0)).className.equals(current_class.className)==false) + FuncType current_func = (FuncType) type; + List params = current_func.parameters; + if(name.equals("__init__") ) + if( params.size() != 1 || + !(params.get(0) instanceof ClassValueType)|| + !((ClassValueType)params.get(0)).className().equals(current_class.className)) + errors.semError(id, "Method overridden with different type signature: __init__"); + else sym.put(name, current_func); + if(params.size() < 1 || (params.get(0) instanceof ClassValueType==false) || ((ClassValueType)params.get(0)).className().equals(current_class.className)==false) errors.semError( - id, "First parameter of method must be of same class: %s", name); - if (super_syms.contains(name)) + id, "First parameter of the following method must be of the enclosing class: %s", name); + if(curr_syms.contains(name)){ + errors.semError(id, "Duplicate declaration of identifier in same scope: %s", name); + } + else if (super_syms.contains(name)) { - if ((super_scope.get(id.name) instanceof FuncValueType)==false) - errors.semError(id, "Cannot re-define attribute: %s" + name); + if ((super_scope.get(id.name) instanceof FuncType)==false) + errors.semError(id, "Cannot re-define attribute: %s", name); else { - FuncValueType super_func = (FuncValueType) super_scope.get(id.name); - FuncValueType current_func = (FuncValueType) type; + FuncType super_func = (FuncType) super_scope.get(id.name); if (compare_functions(super_func, current_func)) - sym.put(name, type); + sym.put(name, current_func); else errors.semError( - id, "Method overridden with different type signature: %s" + name); + id, "Method overridden with different type signature: %s", name); } } else - sym.put(name, type); - } + sym.put(name, current_func); + } else if (super_syms.contains(name)) + errors.semError(id, "Cannot re-define attribute: %s", name); + else + sym.put(name, type); + curr_syms.add(name); } } - for (String super_sym : super_syms) - { - if (sym.getDeclaredSymbols().contains(super_sym)==false) - sym.put(super_sym, super_scope.get(super_sym)); - } + if(super_syms != null) + for (String super_sym : super_syms) + { + if (sym.getDeclaredSymbols().contains(super_sym)==false) + sym.put(super_sym, super_scope.get(super_sym)); + } sym = sym.getParent(); + current_class.scope = current_scope; current_class=null; + putSymChecked(node.name, node.name.name, cvt); return cvt; } + @Override public Type analyze(VarDef node) { Type var_type = sym.get(node.var.identifier.name); - if (node.var.identifier.name!=current_class.className && var_type==null) - errors.semError( - node, "Cannot re-define attribute: %s", node.var.identifier.name); - return ValueType.annotationToValueType(node.var.type); + if(firstPass || (sym != globals && (current_class==null || !sym.equals(current_class.scope)))){ + if (sym != globals && globals.get(node.var.identifier.name)!= null && globals.get(node.var.identifier.name) instanceof ClassVType) //class names are only in global scope + errors.semError(node.var.identifier, "Cannot shadow class name: %s", node.var.identifier.name); + else if(sym.getDeclaredSymbols().contains(node.var.identifier.name)) + errors.semError( + node.var.identifier, "Duplicate declaration of identifier in same scope: %s", node.var.identifier.name); + var_type = ValueType.annotationToValueType(node.var.type); + sym.put(node.var.identifier.name, var_type); + } + Type val_type = node.value.dispatch(typeChecker); + if( !firstPass && var_type instanceof ClassValueType) + { + String className = ((ClassValueType)var_type).className(); + Type varVType = sym.get(className); + if(!(className != null && varVType instanceof ClassVType)) + errors.semError(node.var.type, "Invalid type annotation; there is no class named: %s", (className!=null?className:"")); + else if(val_type!=Type.NONE_TYPE && !StudentAnalysis.subClassOf(varVType,val_type, sym)) + errors.semError(node, "Expected type `%s`; got type `%s`", varVType, val_type); + } + return var_type; } - @Override - public Type analyze(ListType node) - { - return ValueType.annotationToValueType(node); + + public void setScope(SymbolTable currentScope) { + sym = currentScope; + } + public void setCurrClass(ClassVType current_class){ + this.current_class = current_class; } } diff --git a/src/main/java/chocopy/pa2/StudentAnalysis.java b/src/main/java/chocopy/pa2/StudentAnalysis.java index b008a2b..d4c553d 100644 --- a/src/main/java/chocopy/pa2/StudentAnalysis.java +++ b/src/main/java/chocopy/pa2/StudentAnalysis.java @@ -1,9 +1,13 @@ package chocopy.pa2; import chocopy.common.analysis.SymbolTable; +import chocopy.common.analysis.types.ClassVType; +import chocopy.common.analysis.types.ClassValueType; import chocopy.common.analysis.types.Type; +import chocopy.common.analysis.types.ValueType; +import chocopy.common.astnodes.ClassType; import chocopy.common.astnodes.Program; - +import java.util.ArrayList; /** Top-level class for performing semantic analysis. */ public class StudentAnalysis { @@ -11,6 +15,68 @@ public class StudentAnalysis { * Perform semantic analysis on PROGRAM, adding error messages and type annotations. Provide * debugging output iff DEBUG. Returns modified tree. */ + + public static + boolean subClassOf(Type p, Type c, SymbolTable sym){ + String pName = p.className(); + if(pName!=null && pName.equals("object")) + return true; + if(c instanceof ClassValueType) + c = sym.get(c.className()); + if(c instanceof ClassVType){ + ClassVType child = (ClassVType) c; + + String typename = child.className; + while(typename!=null){ + if(typename.equals(pName)) + return true; + child = child.super_class; + if(child!=null) + typename = child.className; + else + return false; + } + return false; + } else return p.equals(c); + } + private static void extractInhPath(Type ty, ArrayList res){ + if(ty == null) + { + res.add(Type.OBJECT_TYPE); + return; + } + if(ty instanceof ClassVType){ + ClassVType t1 = (ClassVType) ty; + String typename = t1.className; + while(typename!=null){ + res.add(new ClassValueType(typename)); + t1 = t1.super_class; + if(t1 != null) + typename = t1.className(); + else break; + } + } else res.add(ty); + if(!res.get(res.size() - 1).equals(Type.OBJECT_TYPE)) + res.add(Type.OBJECT_TYPE); + } + public static Type lowestCommonType(Type p, Type c, SymbolTable sym){ + if(p instanceof ClassValueType) + p = sym.get(p.className()); + if(c instanceof ClassValueType) + c = sym.get(c.className()); + ArrayList inhPath1 = new ArrayList(), + inhPath2 = new ArrayList(); + extractInhPath(p, inhPath1); + extractInhPath(c, inhPath2); + int l1 = inhPath1.size(), l2 = inhPath2.size(), + len = l1 < l2 ? l1 : l2; + int i = 1; + for(; i <= len; ++ i){ + if(!inhPath1.get(l1 - i).equals(inhPath2.get(l2 - i))) + break; + } + return inhPath1.get(l1 - i + 1); + } public static Program process(Program program, boolean debug) { if (program.hasErrors()) { return program; @@ -24,7 +90,7 @@ public class StudentAnalysis { TypeChecker typeChecker = new TypeChecker(globalSym, program.errors); program.dispatch(typeChecker); } - + System.out.println(program); return program; } } diff --git a/src/main/java/chocopy/pa2/TypeChecker.java b/src/main/java/chocopy/pa2/TypeChecker.java index ab8acc1..7b4fe2e 100644 --- a/src/main/java/chocopy/pa2/TypeChecker.java +++ b/src/main/java/chocopy/pa2/TypeChecker.java @@ -2,6 +2,11 @@ package chocopy.pa2; import chocopy.common.analysis.AbstractNodeAnalyzer; import chocopy.common.analysis.SymbolTable; +import chocopy.common.analysis.types.ClassVType; +import chocopy.common.analysis.types.ClassValueType; +import chocopy.common.analysis.types.FuncType; +import chocopy.common.analysis.types.FuncValueType; +import chocopy.common.analysis.types.ListValueType; import chocopy.common.analysis.types.Type; import chocopy.common.analysis.types.ValueType; import chocopy.common.astnodes.*; @@ -9,13 +14,21 @@ import chocopy.common.astnodes.*; import static chocopy.common.analysis.types.Type.INT_TYPE; import static chocopy.common.analysis.types.Type.OBJECT_TYPE; +import java.util.ArrayList; + +import com.fasterxml.jackson.annotation.JacksonInject.Value; + /** * Analyzer that performs ChocoPy type checks on all nodes. Applied after collecting declarations. */ public class TypeChecker extends AbstractNodeAnalyzer { - + // global scope + private final SymbolTable sym; + private final DeclarationAnalyzer declAnalyzer; /** The current symbol table (changes depending on the function being analyzed). */ - private final SymbolTable sym; + private SymbolTable currentScope; + private Type currReturnType; + private boolean returned = false, member = false; /** Collector for errors. */ private final Errors errors; @@ -25,13 +38,23 @@ public class TypeChecker extends AbstractNodeAnalyzer { */ public TypeChecker(SymbolTable globalSymbols, Errors errors0) { sym = globalSymbols; + currentScope = sym; errors = errors0; + currReturnType = null; + declAnalyzer = new DeclarationAnalyzer(errors0, this, globalSymbols); } - /** * Inserts an error message in NODE if there isn't one already. The message is constructed with * MESSAGE and ARGS as for String.format. */ + boolean isVariableType(Type ty){ + return ty.isSpecialType() || ty.equals(Type.OBJECT_TYPE); + } + private Type declAnalyze(Node node){ + //if(currentScope != sym) + declAnalyzer.setScope(currentScope); + return node.dispatch(declAnalyzer); + } private void err(Node node, String message, Object... args) { errors.semError(node, message, args); } @@ -46,6 +69,335 @@ public class TypeChecker extends AbstractNodeAnalyzer { } return null; } + @Override + public Type analyze(ClassDef node){ + ClassVType t = (ClassVType) sym.get(node.name.name); + SymbolTable backScope = currentScope; + currentScope = t.scope; + declAnalyzer.setCurrClass(t); + for(Declaration decl : node.declarations){ + decl.dispatch(this); + } + declAnalyzer.setCurrClass(null); + currentScope = backScope; + return null; + } + @Override + public Type analyze(AssignStmt node) { + Type tr = node.value.dispatch(this); + Type tl; + boolean error = false; + for (Expr ex : node.targets) + { + tl = ex.dispatch(this); + if(error) continue; + else if(tl == null) + { + err(node, "Expression `%s` type inference error.", ex); + error = true; + } else if (ex instanceof IndexExpr && + ((IndexExpr)ex).list.getInferredType().equals(Type.STR_TYPE)){ + err(ex, "`str` is not a list type"); + error = true; + } + else if(tr!=null && tl.isListType() && tr.isListType()){ + if(!((!tl.elementType().isSpecialType()&&tr.elementType().equals(Type.NONE_TYPE))|| + tl.equals(tr)||tr.elementType().equals(Type.EMPTY_TYPE))){ + err(node, "Expected type `%s`; got type `%s`", tl, tr); + error = true; + } + } + else if(tl.isListType() && Type.EMPTY_TYPE.equals(tr)) ; //continue; + else if(tr != null && !(StudentAnalysis.subClassOf(tl, tr, currentScope) || !tl.isSpecialType() && tr.equals(Type.NONE_TYPE))) + { + err(node, "Expected type `%s`; got type `%s`", tl, tr); + error = true; + } + } + return null; + } + + @Override + public Type analyze(BooleanLiteral node) { + return node.setInferredType(Type.BOOL_TYPE); + } + @Override + public Type analyze(FuncDef node) { + returned = false; + Type prevReturnType = this.currReturnType; + currentScope = new SymbolTable<>(currentScope); + this.currReturnType = ValueType.annotationToValueType(node.returnType); + declAnalyze(node); + for(Stmt st : node.statements) + st.dispatch(this); + + if(currReturnType != null && currReturnType.isSpecialType() && !returned) + err(node.name, "All paths in this function/method must have a return statement: %s", node.name.name); + this.currReturnType = prevReturnType; + currentScope = currentScope.getParent(); + return null; + } + + @Override + public Type analyze(CallExpr node) { + Type f = currentScope.get(node.function.name); + ArrayList types = new ArrayList<>(); + for(Expr ex: node.args) + types.add(ex.dispatch(this)); + if(f != null && f.isFuncType()) + { + FuncType fty = (FuncType) f; + int lArgs = node.args.size(), lPars = fty.parameters.size(); + if(lArgs != lPars) + err(node, "Expected %d arguments; got %d", lPars, lArgs); + else{ + for(int i = 0; i < lArgs; ++i){ + Type p = fty.parameters.get(i); + Type c = types.get(i); + if((p.isSpecialType()&&!p.equals(c)) || + (!p.isSpecialType()&&!StudentAnalysis.subClassOf(p, c, currentScope))) + err(node,"Expected type `%s`; got type `%s` in parameter %d", p, c, i); + } + } + node.function.setInferredType(new FuncType(fty.parameters, fty.returnType)); + return node.setInferredType(fty.returnType); + } + else if (f != null && f instanceof ClassVType){ + ClassVType cty = (ClassVType) f; + return node.setInferredType(new ClassValueType(f.className())); + } + else{ + err(node, "Not a function or class: %s", node.function.name); + return node.setInferredType(Type.NONE_TYPE); + } + } + + @Override + public Type analyze(ClassType node) { + return sym.get(node.className); + } + + @Override + public Type analyze(ForStmt node) { + Type iterableType = node.iterable.setInferredType( + node.iterable.dispatch(this)); + if(iterableType == null) + err(node, "Iterable `%s` type inference error.", node.iterable); + else if (iterableType.equals(Type.STR_TYPE)) + node.identifier.setInferredType(Type.STR_TYPE); + else if(iterableType.elementType() == null){ + err(node, "`%s` isn't iterable", iterableType); + } + else + node.identifier.setInferredType( + iterableType.elementType() + ); + for(Stmt st : node.body) + st.dispatch(this); + return null; + } + + @Override + public Type analyze(IfExpr node) { + Type condTy = node.condition.dispatch(this); + if(!condTy.equals(Type.BOOL_TYPE)){ + err(node, "If condition `%s` isn't a boolean expression.", node.condition); + } + + Type ifTy = node.thenExpr.dispatch(this), + elseTy = node.elseExpr.dispatch(this); + + + return node.setInferredType(StudentAnalysis.lowestCommonType(ifTy, elseTy, currentScope)); + } + + @Override + public Type analyze(IfStmt node) { + Type condTy = node.condition.dispatch(this); + if(!condTy.equals(Type.BOOL_TYPE)){ + err(node, "If condition `%s` isn't a boolean expression.", node.condition); + } + boolean prevReturned = returned, thenReturned; + for(Stmt st : node.thenBody) + st.dispatch(this); + thenReturned = prevReturned || returned; + returned = prevReturned; + for(Stmt st : node.elseBody) + st.dispatch(this); + returned = returned && thenReturned; + return null; + } + + @Override + public Type analyze(IndexExpr node) { + Type listTy = node.list.dispatch(this); + if(!(listTy.isListType() || listTy.equals(Type.STR_TYPE))) + err(node, "Cannot index into type `%s`", listTy); + if(!node.index.dispatch(this).equals(Type.INT_TYPE)) + err(node, "Index is of non-integer type `%s`", node.index.getInferredType()); + if(listTy.equals(Type.STR_TYPE)) + return node.setInferredType(Type.STR_TYPE); + else if(listTy.elementType() != null) + return node.setInferredType(listTy.elementType()); + else return node.setInferredType(Type.OBJECT_TYPE); + } + + @Override + public Type analyze(ListExpr node) { + Type t = null; + for(Expr ex : node.elements) + { + Type thisType = ex.dispatch(this); + if(t == null) + t = thisType; + t = StudentAnalysis.lowestCommonType(t, thisType, currentScope); + } + if(t == null) + return node.setInferredType(Type.EMPTY_TYPE); + return node.setInferredType(new ListValueType(t)); + } + + @Override + public Type analyze(MemberExpr node) { + boolean prevIsMember = member; + member = false; + Type ty = node.object.dispatch(this); + if(ty instanceof ClassValueType){ + ty = currentScope.get(((ClassValueType) ty).className()); + if(ty instanceof ClassVType){ + ClassVType classTy = (ClassVType) ty; + Type type = classTy.scope.get(node.member.name); + if(type != null) + return node.setInferredType(type); + else + err(node, "There is no %s named `%s` in class `%s`", + prevIsMember?"method":"attribute", node.member.name, classTy); + } else + err(node, "Class `%s` undefined", ty.className()); + } + else + err(node, "`%s` isn't a class.", ty); + return node.setInferredType(ValueType.OBJECT_TYPE); + } + + @Override + public Type analyze(MethodCallExpr node) { + boolean prevIsMember = member; + member = true; + Type ty = node.method.dispatch(this); + member = prevIsMember; + Type thisTy = Type.OBJECT_TYPE; + if(ty instanceof FuncType){ + FuncType funcTy = (FuncType) ty; + int len = funcTy.parameters.size() - 1, largs = node.args.size(); + if(largs != len) + err(node, "Expected %d arguments; got %d", len, largs); + len = len<=largs?len:largs; + for(int i = 0; i < len; ++i){ + Expr thisArg = node.args.get(i); + Type thisArgTy = thisArg.setInferredType(thisArg.dispatch(this)), + thisParamTy = funcTy.parameters.get(i + 1); + + if(!thisParamTy.equals(thisArgTy)) + err(node, "Expected type `%s`; got type `%s` in parameter %d", + thisParamTy, thisArgTy, i + 1); + } + thisTy = funcTy.returnType; + } + // else + // err(node.method.member, "`%s` isn't a MemberFunction.", node.method.member.name); + + for(Expr args : node.args) + args.dispatch(this); + + return node.setInferredType(thisTy); + } + + @Override + public Type analyze(NoneLiteral node) { + return node.setInferredType(Type.NONE_TYPE); + } + + @Override + public Type analyze(NonLocalDecl node) { + SymbolTable parent = currentScope.getParent(); + if(parent == null || parent == sym || + !parent.getDeclaredSymbols().contains(node.variable.name)|| + !isVariableType(parent.get(node.variable.name)) + ){ + err(node.variable, "Not a nonlocal variable: %s", node.variable.name); + } else if(currentScope.getDeclaredSymbols().contains(node.variable.name)){ + errors.semError( + node.variable, "Duplicate declaration of identifier in same scope: %s", node.variable.name); + } + else + { + Type nonlocalVar = parent.get(node.variable.name); + currentScope.put(node.variable.name, nonlocalVar); + } + return null; + } + + @Override + public Type analyze(ReturnStmt node) { + if(node.value != null) + node.value.dispatch(this); + Type p = this.currReturnType; + Type c = (node.value == null ? Type.NONE_TYPE: node.value.getInferredType()); + if(node.value == null && p.isSpecialType()) + err(node, "Expected type `%s`; got `None`", p); + else if(p.isSpecialType()&&(c.equals(Type.NONE_TYPE)) || (!c.equals(Type.NONE_TYPE) && !StudentAnalysis.subClassOf(p, c, currentScope))) + err(node, "Expected type `%s`; got type `%s`", p, c); + returned = true; + return null; + } + + @Override + public Type analyze(StringLiteral node) { + return node.setInferredType(Type.STR_TYPE); + } + + @Override + public Type analyze(TypedVar node) { + return ValueType.annotationToValueType(node.type); + } + + @Override + public Type analyze(VarDef node) { + return declAnalyze(node); + } + + @Override + public Type analyze(UnaryExpr node) { + Type t = node.operand.dispatch(this); + switch (node.operator) { + case "-": + case "+": + if (INT_TYPE.equals(t)) { + return node.setInferredType(INT_TYPE); + } else { + err(node, "Cannot apply operator `%s` on type `%s`", node.operator, t); + return node.setInferredType(INT_TYPE); + } + case "not": + if (!(Type.BOOL_TYPE.equals(t))) + err(node, "Cannot apply operator `not` on type `%s`", t); + return node.setInferredType(Type.BOOL_TYPE); + default: + return node.setInferredType(OBJECT_TYPE); + } + } + + + @Override + public Type analyze(WhileStmt node) { + if(!node.condition.dispatch(this).equals(Type.BOOL_TYPE)) + err(node, "`%s` isn't a boolean expression.", node.condition); + for(Stmt st : node.body) + st.dispatch(this); + return null; + } + @Override public Type analyze(ExprStmt s) { @@ -74,6 +426,43 @@ public class TypeChecker extends AbstractNodeAnalyzer { err(e, "Cannot apply operator `%s` on types `%s` and `%s`", e.operator, t1, t2); return e.setInferredType(INT_TYPE); } + case "+": + if (INT_TYPE.equals(t1) && INT_TYPE.equals(t2)) { + return e.setInferredType(INT_TYPE); + } else if(Type.STR_TYPE.equals(t1) && Type.STR_TYPE.equals(t2)){ + return e.setInferredType(Type.STR_TYPE); + } else if (t1.isListType() && t2.isListType()){ + return e.setInferredType(new ListValueType(StudentAnalysis. + lowestCommonType(t1.elementType(), t2.elementType(), currentScope))); + }else if (t1.isListType() && t2.equals(Type.EMPTY_TYPE)) + return e.setInferredType(t1); + else { + err(e, "Cannot apply operator `+` on types `%s` and `%s`", t1, t2); + return e.setInferredType(INT_TYPE); + } + case "and": + case "or": + if (!(Type.BOOL_TYPE.equals(t1) && Type.BOOL_TYPE.equals(t2))) + err(e, "Cannot apply operator `%s` on types `%s` and `%s`", e.operator, t1, t2); + return e.setInferredType(Type.BOOL_TYPE); + case ">": + case "<": + case ">=": + case "<=": + if (!(INT_TYPE.equals(t1) && INT_TYPE.equals(t2))) + err(e, "Cannot apply operator `%s` on types `%s` and `%s`", e.operator, t1, t2); + return e.setInferredType(Type.BOOL_TYPE); + case "!=": + case "==": + if (!(INT_TYPE.equals(t1) && INT_TYPE.equals(t2) + || Type.BOOL_TYPE.equals(t1) && Type.BOOL_TYPE.equals(t2) + || Type.STR_TYPE.equals(t1) && Type.STR_TYPE.equals(t2))) + err(e, "Cannot apply operator `%s` on types `%s` and `%s`", e.operator, t1, t2); + return e.setInferredType(Type.BOOL_TYPE); + case "is": + if(t1.isSpecialType()||t2.isSpecialType()) + err(e, "Cannot apply operator `%s` on types `%s` and `%s`", e.operator, t1, t2); + return e.setInferredType(Type.BOOL_TYPE); default: return e.setInferredType(OBJECT_TYPE); } @@ -82,7 +471,10 @@ public class TypeChecker extends AbstractNodeAnalyzer { @Override public Type analyze(Identifier id) { String varName = id.name; - Type varType = sym.get(varName); + Type varType = currentScope.get(varName); + if(varType!=null && !currentScope.getDeclaredSymbols().contains(varName)){ + err(id, "Cannot assign to variable that is not explicitly declared in this scope: %s", varName); + } if (varType != null && varType.isValueType()) { return id.setInferredType(varType); @@ -91,4 +483,26 @@ public class TypeChecker extends AbstractNodeAnalyzer { err(id, "Not a variable: %s", varName); return id.setInferredType(ValueType.OBJECT_TYPE); } + + @Override + public Type analyze(GlobalDecl node) + { + Type ty = sym.get(node.variable.name); + if (sym.declares(node.variable.name)==false || + !isVariableType(ty) + ) + { + errors.semError( + node.variable, "Not a global variable: %s", node.variable.name); + return null; + } + else if(currentScope.getDeclaredSymbols().contains(node.variable.name)){ + errors.semError( + node.variable, "Duplicate declaration of identifier in same scope: %s", node.variable.name); + } + else + currentScope.put(node.variable.name, ty); + return ty; + } + }