Skip to content

Commit 5bdd26c

Browse files
authored
Merge pull request #684 from viperproject/meilers_type_errors
Better type error reporting
2 parents ae4a123 + 519e942 commit 5bdd26c

3 files changed

Lines changed: 99 additions & 44 deletions

File tree

src/main/scala/viper/silver/parser/ParseAst.scala

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package viper.silver.parser
88

99
import java.util.concurrent.atomic.{AtomicInteger, AtomicLong}
10-
1110
import viper.silver.ast.utility.Visitor
1211
import viper.silver.ast.utility.rewriter.{Rewritable, StrategyBuilder}
1312
import viper.silver.ast.{Exp, MagicWandOp, Member, NoPosition, Position, Stmt, Type}
@@ -243,6 +242,8 @@ case class PDomainType(domain: PIdnUse, args: Seq[PType])(val pos: (Position, Po
243242
r
244243
}
245244

245+
override def withTypeArguments(s: Seq[PType]) = copy(args = s)(pos)
246+
246247
override def toString = domain.name + (if (args.isEmpty) "" else s"[${args.mkString(", ")}]")
247248
}
248249

@@ -292,6 +293,8 @@ trait PGenericType extends PType {
292293
def typeArguments : Seq[PType]
293294
override def isGround = typeArguments.forall(_.isGround)
294295
override def toString = s"$genericName[${typeArguments.mkString(", ")}]"
296+
297+
def withTypeArguments(s: Seq[PType]) : PGenericType
295298
}
296299

297300
sealed trait PGenericCollectionType extends PGenericType {
@@ -304,14 +307,20 @@ sealed trait PGenericCollectionType extends PGenericType {
304307
case class PSeqType(elementType: PType)(val pos: (Position, Position) = (NoPosition, NoPosition)) extends PType with PGenericCollectionType {
305308
override val genericName = "Seq"
306309
override def substitute(map: PTypeSubstitution) = PSeqType(elementType.substitute(map))(pos)
310+
311+
override def withTypeArguments(s: Seq[PType]) = copy(elementType = s.head)(pos)
307312
}
308313
case class PSetType(elementType: PType)(val pos: (Position, Position) = (NoPosition, NoPosition)) extends PType with PGenericCollectionType {
309314
override val genericName = "Set"
310315
override def substitute(map: PTypeSubstitution) = PSetType(elementType.substitute(map))(pos)
316+
317+
override def withTypeArguments(s: Seq[PType]) = copy(elementType = s.head)(pos)
311318
}
312319
case class PMultisetType(elementType: PType)(val pos: (Position, Position) = (NoPosition, NoPosition)) extends PType with PGenericCollectionType {
313320
override val genericName = "Multiset"
314321
override def substitute(map: PTypeSubstitution) = PMultisetType(elementType.substitute(map))(pos)
322+
323+
override def withTypeArguments(s: Seq[PType]): PMultisetType = copy(elementType = s.head)(pos)
315324
}
316325

317326
case class PMapType(keyType : PType, valueType : PType)(val pos: (Position, Position) = (NoPosition, NoPosition)) extends PType with PGenericType {
@@ -321,6 +330,8 @@ case class PMapType(keyType : PType, valueType : PType)(val pos: (Position, Posi
321330

322331
override def isValidOrUndeclared = typeArguments.forall(_.isValidOrUndeclared)
323332
override def substitute(map: PTypeSubstitution) = PMapType(keyType.substitute(map), valueType.substitute(map))(pos)
333+
334+
override def withTypeArguments(s: Seq[PType]): PMapType = copy(keyType = s.head, valueType = s(1))(pos)
324335
}
325336

326337
/** Type used for internal nodes (e.g. typing predicate accesses) - should not be
@@ -382,27 +393,42 @@ class PTypeSubstitution(val m:Map[String,PType]) //extends Map[String,PType]()
382393
val ts = PTypeSubstitution(Map(a -> b))
383394
PTypeSubstitution(m.map(kv => kv._1 -> kv._2.substitute(ts)))
384395
}
385-
def *(other:PTypeSubstitution) : Option[PTypeSubstitution] =
386-
other.m.foldLeft(Some(this):Option[PTypeSubstitution])({
387-
case (Some(s),p)=>s.add(PTypeVar(p._1),p._2);
388-
case (None,_) => None })
389396

390-
def add(a:String,b:PType): Option[PTypeSubstitution] = add(PTypeVar(a),b)
397+
// The following methods all return a type substitution if successful,
398+
// otherwise a pair containing the expected and the found type.
399+
def *(other:PTypeSubstitution) : Either[(PType, PType), PTypeSubstitution] =
400+
other.m.foldLeft(Right(this):Either[(PType, PType), PTypeSubstitution])({
401+
case (Right(s),p)=>s.add(PTypeVar(p._1),p._2);
402+
case (l@Left(_),_) => l })
403+
404+
def add(a:String,b:PType): Either[(PType, PType), PTypeSubstitution] = add(PTypeVar(a),b)
391405

392-
def add(a:PType,b:PType): Option[PTypeSubstitution] = {
406+
def add(a:PType,b:PType): Either[(PType, PType), PTypeSubstitution] = {
393407
val as = a.substitute(this)
394408
val bs = b.substitute(this)
395409
(as, bs) match {
396-
case (aa,bb) if aa == bb => Some(this)
397-
case (PTypeVar(name), t) if PTypeVar.isFreePTVName(name) => assert(!contains(name)); Some(substitute(name,t)+(name->t))
410+
case (aa,bb) if aa == bb => Right(this)
411+
case (PTypeVar(name), t) if PTypeVar.isFreePTVName(name) => assert(!contains(name)); Right(substitute(name,t)+(name->t))
398412
case (_, PTypeVar(name)) if PTypeVar.isFreePTVName(name) => add(bs,as)
399413
case (gt1: PGenericType, gt2: PGenericType) if gt1.genericName == gt2.genericName =>
400-
((gt1.typeArguments zip gt2.typeArguments).foldLeft[Option[PTypeSubstitution]](Some(this))
401-
((ss: Option[PTypeSubstitution], p: (PType, PType)) => ss match {
402-
case Some(sss) => sss.add(p._1,p._2)
403-
case None => None
414+
val zippedArgs = gt1.typeArguments zip gt2.typeArguments
415+
(zippedArgs.foldLeft[Either[(PType, PType), PTypeSubstitution]](Right(this))
416+
((ss: Either[(PType, PType), PTypeSubstitution], p: (PType, PType)) => ss match {
417+
case Right(sss) => sss.add(p._1,p._2) match {
418+
case l@Left(pair) =>
419+
val problemArg = zippedArgs.zipWithIndex.find(_._1 == pair)
420+
problemArg match {
421+
case None => l
422+
case Some((_, index)) =>
423+
val newArgs = zippedArgs.updated(index, pair)
424+
val (argsA, argsB) = newArgs.unzip
425+
Left(gt1.withTypeArguments(argsA), gt1.withTypeArguments(argsB))
426+
}
427+
case r => r
428+
}
429+
case Left((aa, bb)) => Left((aa, bb))
404430
}))
405-
case _ => None
431+
case (aa, bb) => Left((aa, bb))
406432
}
407433

408434
}
@@ -516,7 +542,7 @@ case class PCall(func: PIdnUse, args: Seq[PExp], typeAnnotated : Option[PType] =
516542
assert(s3.m.forall(_._2.isGround))
517543
domainSubstitution = Some(s3)
518544
dtr.mm.values.foldLeft(ots)(
519-
(tss,s)=> if (tss.contains(s)) tss else tss.add(s, PTypeSubstitution.defaultType).get)
545+
(tss,s)=> if (tss.contains(s)) tss else tss.add(s, PTypeSubstitution.defaultType).getOrElse(null))
520546
case _ => ots
521547
}
522548
super.forceSubstitution(ts)

src/main/scala/viper/silver/parser/Resolver.scala

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,12 @@ case class TypeChecker(names: NameAnalyser) {
459459
* Type-check and resolve e and ensure that it has type expected. If that is not the case, then an
460460
* error should be issued.
461461
*/
462-
def composeAndAdd(pts1: PTypeSubstitution,pts2: PTypeSubstitution,pt1:PType,pt2:PType) : Option[PTypeSubstitution] = {
462+
def composeAndAdd(pts1: PTypeSubstitution,pts2: PTypeSubstitution,pt1:PType,pt2:PType) : Either[(PType, PType), PTypeSubstitution] = {
463463
val sharedKeys = pts1.keySet.intersect(pts2.keySet)
464464
if (sharedKeys.exists(p => pts1.get(p).get != pts2.get(p).get)) {
465465
/* no composed substitution if input substitutions do not match */
466-
return None
466+
val nonMatchingKey = sharedKeys.find(p => pts1.get(p).get != pts2.get(p).get).get
467+
return Left((pts1.get(nonMatchingKey).get, pts2.get(nonMatchingKey).get))
467468
}
468469

469470
//composed substitution before add
@@ -472,28 +473,45 @@ case class TypeChecker(names: NameAnalyser) {
472473
pts2.map({ case (s: String, pt: PType) => s -> pt.substitute(pts1) }))
473474
cs.add(pt1,pt2)
474475
}
476+
477+
/*
478+
* Parameters:
479+
* rlts: local substitutions, refreshed
480+
* argData: a sequence of tuples, one per op arguments, where
481+
* _1 is the fresh local argument type
482+
* _2 is the type of the argument expression
483+
* _3 is the set of substitutions of the argument expression
484+
* _4 is the argument expression itself (used to extract a precise position)
485+
* Returns:
486+
* Either a new type substitution (right case) or, in case of failure (left) a triple containing
487+
* _1 the expected type
488+
* _2 the found type
489+
* _3 the argument that caused the failure
490+
*/
475491
def unifySequenceWithSubstitutions(
476-
rlts: Seq[PTypeSubstitution], //local substitutions, refreshed
477-
argData: scala.collection.immutable.Seq[(PType, PType, Seq[PTypeSubstitution])]) : Seq[PTypeSubstitution]
478-
// a sequence of triples, one per op arguments, where
479-
//_1 is the fresh local argument type
480-
//_2 is the type of the argument expression
481-
//_3 is the set of substitutions of the argument expression
492+
rlts: Seq[PTypeSubstitution],
493+
argData: scala.collection.immutable.Seq[(PType, PType, Seq[PTypeSubstitution], PExp)]) : Either[(PType, PType, PExp), Seq[PTypeSubstitution]]
482494
= {
483495
var pss = rlts
484496
for (tri <- argData){
485-
val current = (for (ps <- pss; aps <- tri._3) yield composeAndAdd(ps, aps, tri._1, tri._2))
486-
pss = current.flatten
497+
val current = (for (ps <- pss; aps <- tri._3)
498+
yield composeAndAdd(ps, aps, tri._1, tri._2))
499+
val allBad = current.forall(e => e.isLeft)
500+
if (allBad) {
501+
val badMatch = current.find(e => e.isLeft)
502+
val badTypes = badMatch.get.swap.toOption.get
503+
return Left(badTypes._1, badTypes._2, tri._4)
504+
}
505+
pss = current.flatMap(_.toOption)
487506
}
488-
pss
489-
//(a:Set[PTypeSubstitution], e:Option[PTypeSubstitution])=>{
507+
Right(pss)
490508
}
491509

492510
def ground(exp: PExp, pts: PTypeSubstitution) : PTypeSubstitution =
493511
pts.m.flatMap(kv=>kv._2.freeTypeVariables &~ pts.m.keySet).foldLeft(pts)((ts,fv)=>
494512
{
495513
messages ++= FastMessaging.message(exp, s"Unconstrained type parameter, substituting default type ${PTypeSubstitution.defaultType}.", error=false)
496-
ts.add(PTypeVar(fv),PTypeSubstitution.defaultType).get
514+
ts.add(PTypeVar(fv),PTypeSubstitution.defaultType).toOption.get
497515
}
498516
)
499517

@@ -517,7 +535,7 @@ case class TypeChecker(names: NameAnalyser) {
517535
checkInternal(exp)
518536
if (exp.typ.isValidOrUndeclared && exp.typeSubstitutions.nonEmpty) {
519537
val etss = oexpected match {
520-
case Some(expected) if expected.isValidOrUndeclared => exp.typeSubstitutions.flatMap(_.add(exp.typ, expected))
538+
case Some(expected) if expected.isValidOrUndeclared => exp.typeSubstitutions.flatMap(_.add(exp.typ, expected).toOption)
521539
case _ => exp.typeSubstitutions
522540
}
523541
if (etss.nonEmpty) {
@@ -526,7 +544,12 @@ case class TypeChecker(names: NameAnalyser) {
526544
} else {
527545
oexpected match {
528546
case Some(expected) =>
529-
messages ++= FastMessaging.message(exp, s"Expected type ${expected.toString}, but found ${exp.typ.toString} at the expression at ${exp.pos._1}")
547+
val reportedActual = if (exp.typ.isGround) {
548+
exp.typ
549+
} else {
550+
exp.typ.substitute(selectAndGroundTypeSubstitution(exp, exp.typeSubstitutions))
551+
}
552+
messages ++= FastMessaging.message(exp, s"Expected type ${expected.toString}, but found ${reportedActual} at the expression at ${exp.pos._1}")
530553
case None =>
531554
typeError(exp)
532555
}
@@ -713,19 +736,23 @@ case class TypeChecker(names: NameAnalyser) {
713736
assert(rlts.nonEmpty)
714737
val rrt: PDomainType = POpApp.pRes.substitute(ltr).asInstanceOf[PDomainType] // return type (which is a dummy type variable) replaced with fresh type
715738
val flat = poa.args.indices map (i => POpApp.pArg(i).substitute(ltr)) //fresh local argument types
716-
// the triples below are: (fresh argument type, argument type as used in domain of substitutions, substitutions)
717-
poa.typeSubstitutions ++= unifySequenceWithSubstitutions(rlts, flat.indices.map(i => (flat(i), poa.args(i).typ, poa.args(i).typeSubstitutions.distinct.toSeq)) ++
739+
// the quadruples below are: (fresh argument type, argument type as used in domain of substitutions, substitutions, expression)
740+
val argData = flat.indices.map(i => (flat(i), poa.args(i).typ, poa.args(i).typeSubstitutions.distinct.toSeq, poa.args(i))) ++
718741
(
719742
extraReturnTypeConstraint match {
720743
case None => Nil
721-
case Some(t) => Seq((rrt, t, List(PTypeSubstitution.id)))
744+
case Some(t) => Seq((rrt, t, List(PTypeSubstitution.id), poa))
722745
}
723746
)
724-
)
725-
val ts = poa.typeSubstitutions.distinct
726-
if (ts.isEmpty)
727-
typeError(poa)
728-
poa.typ = if (ts.size == 1) rrt.substitute(ts.head) else rrt
747+
val unifiedSequence = unifySequenceWithSubstitutions(rlts, argData)
748+
if (unifiedSequence.isLeft && poa.typeSubstitutions.isEmpty) {
749+
val problem = unifiedSequence.swap.toOption.get
750+
messages ++= FastMessaging.message(problem._3, s"Type error in the expression at ${problem._3.pos._1}. Expected type ${problem._1} but found ${problem._2}.")
751+
} else {
752+
poa.typeSubstitutions ++= unifiedSequence.toOption.get
753+
val ts = poa.typeSubstitutions.distinct
754+
poa.typ = if (ts.size == 1) rrt.substitute(ts.head) else rrt
755+
}
729756
} else {
730757
poa.typeSubstitutions.clear()
731758
poa.typ = PUnknown()()
@@ -749,7 +776,7 @@ case class TypeChecker(names: NameAnalyser) {
749776
ns.variable.typ = e.typ
750777
checkInternal(pl.body)
751778
pl.typ = pl.body.typ
752-
pl._typeSubstitutions = (for (ts1 <- pl.body.typeSubstitutions;ts2 <- e.typeSubstitutions) yield ts1*ts2).flatten.toList.distinct
779+
pl._typeSubstitutions = (for (ts1 <- pl.body.typeSubstitutions;ts2 <- e.typeSubstitutions) yield (ts1*ts2).toOption).flatten.toList.distinct
753780
curMember = oldCurMember
754781

755782
case pq: PForPerm =>

src/main/scala/viper/silver/plugin/standard/adt/AdtPASTExtension.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ case class PAdtType(adt: PIdnUse, args: Seq[PType])
240240
}
241241
}
242242

243+
override def withTypeArguments(s: Seq[PType]): PGenericType = copy(args = s)(pos)
244+
243245
override def toString: String = adt.name + (if (args.isEmpty) "" else s"[${args.mkString(", ")}]")
244246

245247
}
@@ -282,7 +284,7 @@ sealed trait PAdtOpApp extends PExtender with POpApp {
282284
assert(s3.m.forall(_._2.isGround))
283285
adtSubstitution = Some(s3)
284286
dtr.mm.values.foldLeft(ots)(
285-
(tss, s) => if (tss.contains(s)) tss else tss.add(s, PTypeSubstitution.defaultType).get)
287+
(tss, s) => if (tss.contains(s)) tss else tss.add(s, PTypeSubstitution.defaultType).toOption.get)
286288
case _ => ots
287289
}
288290
super.forceSubstitution(ts)
@@ -378,15 +380,15 @@ object PAdtOpApp {
378380
assert(rlts.nonEmpty)
379381
val rrt: PDomainType = POpApp.pRes.substitute(ltr).asInstanceOf[PDomainType] // return type (which is a dummy type variable) replaced with fresh type
380382
val flat = poa.args.indices map (i => POpApp.pArg(i).substitute(ltr)) //fresh local argument types
381-
// the triples below are: (fresh argument type, argument type as used in domain of substitutions, substitutions)
382-
poa.typeSubstitutions ++= t.unifySequenceWithSubstitutions(rlts, flat.indices.map(i => (flat(i), poa.args(i).typ, poa.args(i).typeSubstitutions.distinct.toSeq)) ++
383+
// the tuples below are: (fresh argument type, argument type as used in domain of substitutions, substitutions, the argument itself)
384+
poa.typeSubstitutions ++= t.unifySequenceWithSubstitutions(rlts, flat.indices.map(i => (flat(i), poa.args(i).typ, poa.args(i).typeSubstitutions.distinct.toSeq, poa.args(i))) ++
383385
(
384386
extraReturnTypeConstraint match {
385387
case None => Nil
386-
case Some(t) => Seq((rrt, t, List(PTypeSubstitution.id)))
388+
case Some(t) => Seq((rrt, t, List(PTypeSubstitution.id), poa))
387389
}
388390
)
389-
)
391+
).getOrElse(Seq())
390392
val ts = poa.typeSubstitutions.distinct
391393
if (ts.isEmpty)
392394
t.typeError(poa)

0 commit comments

Comments
 (0)