Commits

kevinclancy committed 47843c3

began changing refinement merging in order to generate proper errors
when new globals and fields are added.

Comments (0)

Files changed (6)

LoveStudio/LuaAnalyzer/Annotations.fs

 
     let description = (!s).Substring(!i).Trim()
 
-    Param(name,description,typeName)
+    Param(name,typeName,description)
 
 let parseAnnotationField (annotation : string) =
     s := annotation

LoveStudio/LuaAnalyzer/Context.fs

     member this.HasValPath (path : List<string>) : bool =
         (this.FieldFromPath path).IsSome
 
+    member this.IsNewField (path : List<string>) : bool =
+        let rec isNewField (path : List<string>) (field : Field) =
+            match path with
+            | head :: rest when not rest.IsEmpty ->
+                match Type.Coerce this.tenv field.ty with
+                | RecordTy(_,_,_,_,fields,_,_)
+                | OpenRecordTy(_,_,_,_,fields,_,_) ->
+                    if fields.ContainsKey head then
+                        isNewField rest fields.[head]
+                    else
+                        false
+                | _ ->
+                    false
+            | [last] ->
+                match Type.Coerce this.tenv field.ty with
+                | OpenRecordTy(_,_,_,_,fields,_,_) when not (fields.ContainsKey last) ->
+                    true
+                | _ ->
+                    false
+            | [] ->
+                false
+
+        if this.venv.ContainsKey path.[0] then
+            if path.Tail.IsEmpty then
+                false
+            else
+                isNewField path.Tail this.venv.[path.[0]]
+        else
+            if path.Length = 1 then
+                //for globals
+                true 
+            else
+                false
+
     member this.FieldFromPath (path : List<string>) : Option<Field> =
         Contract.Requires(path.Length > 0)
         let rec fieldFromPath (path : List<string>) (field : Field) =

LoveStudio/LuaAnalyzer/ErrorList.fs

 
 /// addError(fileName,msg,rng)
 let addError (fileName : string) (msg : string) (rng : int*int) =
-    errors.Add(fileName,msg,rng)
+    errors.Add(fileName,msg,rng)

LoveStudio/LuaAnalyzer/Type.fs

                 EmptyAnnotation
 
         if paramList.Length > 0 || retList.Length > 0 then
-            let getParamInfo ((name,desc,tyName) : ParamAnnotation) =
+            let getParamInfo ((name,tyName,desc) : ParamAnnotation) =
                 (name,desc,Type.FromString tyName)
             let typedFormals = List.map getParamInfo paramList
             let typedRets = List.map (fun (name,desc) -> desc,Type.FromString name) retList

LoveStudio/LuaAnalyzer/Typechecker.fs

         let opRecVar = venv.Item opRecName
         match Type.Coerce tenv opRecVar.ty with
         | OpenRecordTy(name,desc,srcExp,metamethods,fieldMap,methodMap,loc) ->
-            let field = {Field.OfType(targetType) with desc = targetDesc; isConst = targetConst }
+            let field = {Field.OfType(targetType) with desc = targetDesc; isConst = targetConst; loc = !currentFileName,getExprRange lexp }
             Some ([opRecName;keyName],field)
         | _ ->
             failwith ""
                 None
         | None ->
             if ctxt.addGlobals then
-                Some([var], { Field.OfType(targetType) with isConst = targetConst; desc= targetDesc })
+                Some([var], { Field.OfType(targetType) with isConst = targetConst; desc= targetDesc; loc = !currentFileName, rng })
             else
                 None
     | _ ->
         let value = ctxt.venv.[path.Head]
         let isNewField = not (ctxt.HasValPath path) 
         ctxt.AddValue(path.Head, { value with ty = Type.ApplyDeduction ctxt.tenv value path.Tail target isNewField }) 
+    elif path.Length = 1 && ctxt.addGlobals then
+        ctxt.AddValue(path.Head, target) 
     else
         ctxt
 
     else
         undoRefinement ctxt path (Field.OfType(UnknownTy))
 
+
+
 let rec getRefsFromNegCond (ctxt : Context) (cond : TypedExpr) : List<List<string>*Field> =
     let venv, tenv = ctxt.venv, ctxt.tenv
     match cond with
     | _ ->
         ctxt
 
+let getPredRefinements (ctxt : Context) (clauses : List<TypedExpr*TypedStatement>) (i : int) =
+    // Maps a set of clause refinements extracted from the body to
+    // that set unioned with those refinements made by negating
+    // the conditions in all clauses preceding it.
+
+    let condRefs = List.collect (fun j -> getRefsFromNegCond ctxt (fst clauses.[j])) [0..i-1]
+    List.fold (fun (m : Map<List<string>,Field>) ref -> m.Add ref) Map.empty condRefs 
+
+
 /// For an if statement, we merge the clauses made by deductions as follows:
 /// If all clauses assign some path to some type, we leave that as a deduction
 /// If some path is assigned to different types by different clauses, we undo 
 /// deductions on that path.
-let mergeClauseRefinements (ctxt : Context) (clauseRefinementMaps : List<Map<List<string>, Field>>) =
+let mergeClauseRefinements (ctxt : Context) (clauseRefinementMaps : List<Map<List<string>, Field>>) (isComplete : bool) =
     let tenv = ctxt.tenv
 
     // collects all paths mentioned in clause refinements returning them as a set
     let collectPaths (clauseRefinements : Map<List<string>, Field>) =
-        let foldPath (acc : Set<List<string>>) (path : List<string>) (_ : Field) =
+        let foldPath (acc : Set<List<string>>) (path : List<string>) (f : Field) =
             acc.Add(path)
 
         Map.fold foldPath Set.empty clauseRefinements
 
     // all paths mentioned in assignments in any clause
-    let allPaths = 
+    let allRefinement = 
         let clausePaths = List.map collectPaths clauseRefinementMaps
-        Set.unionMany clausePaths
+        List.fold cover Map.empty clauseRefinements
+        
 
     /// Gets a list of all refinements about the variables mentioned in the if clauses
     /// which had been made before descending into the if statement.
 
     let mapRefinement (path : List<string>) =
         let mapClause (m : Map<List<string>,Field>) =
-            if m.ContainsKey path then
-                m.[path]
-            else
-                let f = ctxt.FieldFromPath path
-                match f with
-                | Some{desc=_;ty=DeducedTy(_,temp);isConst=_;loc=_} ->
-                    { f.Value with ty = temp }
-                | _ ->
-                    Field.OfType(UnknownTy)
+            let f = 
+                if m.ContainsKey path then
+                    m.[path]
+                else
+                    if (ctxt.FieldFromPath path).IsSome then
+                        (ctxt.FieldFromPath path).Value
+                    else
+                        if ctxt.trackErrors && not isComplete then
+
+                        Field.OfType(UnknownTy)
+
+            match f with
+            | {desc=_;ty=DeducedTy(_,temp);isConst=_;loc=_} ->
+                { f with ty = temp }
+            | _ ->
+                f
                     
         let refFields = List.map mapClause clauseRefinementMaps 
 
+        if ctxt.IsNewField path then
+            if not isComplete then
+
+
         if (List.tryFind (fun f -> f.ty = UnknownTy) refFields).IsSome then
             None
         else
             if List.forall (fun f -> Type.IsEqual tenv hd.ty f.ty) refFields then
                 Some hd
             else
+                if ctxt.IsNewField path && ctxt.trackErrors then
+                    addError 
+                        !currentFileName
+                        "field is not assigned same type along all branches of if (maybe adding annotations will help)"
+                        (snd hd.loc)
                 None
 
     let foldPath (acc : Map<List<string>,Option<Field>>) (path : List<string>) =
 
     match stat with
     | If(clauses,rng) ->
+        //TODO: doesn't this belong in getAllRefinements?
+
+        let getClauseRefinements (i : int) =
+            let cond,body = clauses.[i]
+            let predRefinements = getPredRefinements ctxt clauses i
+            let ctxt = Map.fold (fun ctxt path field -> applyRefinement ctxt path field) ctxt predRefinements
+            let ctxt = refineContextFromCond ctxt cond
+            getAllRefinements ctxt Set.empty body
+        
+        let isComplete =
+            match clauses.[clauses.Length-1] with
+            | (True(_,_),_) ->
+                true
+            | _ ->
+                false
+
         // For each variable that is refined in any of the clauses, we 
         // conservatively undo the refinement on that variable. For example,
         // a nillable number variable that is refined to number in the context
         // could be assigned to nil in some clause, in which case the refinement
         // must be removed.
-        let clauseRefinements = List.map (getAllRefinements ctxt Set.empty) (List.map snd clauses)
+        let clauseRefinements = List.init clauses.Length getClauseRefinements
         
         // Maps a set of clause refinements extracted from the body to
         // that set unioned with those refinements made by negating
         // the conditions in all clauses preceding it.
         let addNegCondRefinements (i : int) (refs : Map<List<string>,Field>) =
-            let condRefs = List.collect (fun j -> getRefsFromNegCond ctxt (fst clauses.[j])) [0..i-1]
-            List.fold (fun (m : Map<List<string>,Field>) ref -> m.Add ref) refs condRefs 
+            let condRefs = getPredRefinements ctxt clauses i
+            Map.fold (fun (m : Map<List<string>,Field>) path field -> m.Add(path,field)) refs condRefs
 
         let clauseRefinements = List.mapi addNegCondRefinements clauseRefinements
-        let merged = mergeClauseRefinements ctxt clauseRefinements
+        let merged = mergeClauseRefinements ctxt clauseRefinements isComplete
         Map.fold handleRefinementUpdate ctxt merged
     | Sequence(s0, s1, rng) ->
         let ctxt = updateCtxt ctxt s0
     let venv,tenv = ctxt.venv, ctxt.tenv
     match stat with
     | If(clauses,_) ->
-        let checkClause ((cond,body) : TypedExpr*TypedStatement) = 
+        let checkClause (i : int) ((cond,body) : TypedExpr*TypedStatement) = 
             let condTy,_ = typeCheckExpr ctxt.IsNotLeftExpr cond
             ignore (checkType ctxt cond condTy BoolTy)
+            let predRefinements = getPredRefinements ctxt clauses i
+            let ctxt = Map.fold (fun ctxt path field -> applyRefinement ctxt path field) ctxt predRefinements
             let ctxt = refineContextFromCond ctxt cond
             typeCheckStat ctxt body
             
-        List.iter checkClause clauses
+        List.iteri checkClause clauses
     | While(cond,body,_) ->
         let condTy,_ = typeCheckExpr ctxt.IsNotLeftExpr cond
         ignore (checkType ctxt cond condTy BoolTy)

LoveStudio/LuaAnalyzer/TypedSyntax.fs

             let getParamInfo (formal : Expr) =
                 match formal with
                 | Syntax.NameExpr(f,rng) ->
-                    match List.tryFind (fun (name,desc,tyName) -> name = f) paramList with
-                    | Some (name,desc,tyName) ->
+                    match List.tryFind (fun (name,tyName,desc) -> name = f) paramList with
+                    | Some (name,tyName,desc) ->
                         (f,desc,Type.FromString tyName)
                     | None ->
                         (f,"",UnknownTy)