diff --git a/src/main/resources/manuals/types b/src/main/resources/manuals/types index 86fb735b31..e3a4ff6a15 100644 --- a/src/main/resources/manuals/types +++ b/src/main/resources/manuals/types @@ -27,12 +27,22 @@ type CompletionRecord { // https://tc39.es/ecma262/#sec-completion-record-specification-type type NormalCompletion extends CompletionRecord { Type: Enum[~normal~]; + Value; Target: Enum[~empty~]; } // https://tc39.es/ecma262/#sec-completion-record-specification-type type AbruptCompletion extends CompletionRecord { Type: Enum[~break~, ~continue~, ~return~, ~throw~]; + Value; + Target: String | Enum[~empty~]; +} + +// https://tc39.es/ecma262/2024/#sec-throwcompletion +type ThrowCompletion extends AbruptCompletion { + Type: Enum[~throw~]; + Value: ESValue; + Target: Enum[~empty~]; } // ----------------------------------------------------------------------------- diff --git a/src/main/resources/result/spec-summary b/src/main/resources/result/spec-summary index 8100fc3eaa..5096c9687a 100644 --- a/src/main/resources/result/spec-summary +++ b/src/main/resources/result/spec-summary @@ -16,4 +16,4 @@ - yet: 362 - unknown: 625 - tables: 94 -- type model: 87 \ No newline at end of file +- type model: 88 \ No newline at end of file diff --git a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala index 693e4b82d0..cc65e414ff 100644 --- a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala +++ b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala @@ -284,6 +284,17 @@ class TypeAnalyzer( xs(0).map { x => map += Normal -> Map(x -> RecordT("Constructor")) } AbsValue(retTy, map) }, + "CreateListFromArrayLike" -> { (xs, vs, retTy) => + AbsValue((for { + v <- vs.lift(1) + str = v.ty.list.elem.str + ss <- str match + case Inf => None + case Fin(ss) => Some(ss) + ty = ss.map(ValueTy.fromTypeOf).foldLeft(BotT)(_ || _) + refined = retTy.toValue && NormalT(ListT(ty)) + } yield refined).getOrElse(retTy)) + }, "IsUnresolvableReference" -> { (xs, vs, retTy) => var map: Refinements = Map() xs(0).map { x => @@ -655,17 +666,7 @@ class TypeAnalyzer( rty = rv.ty refinedV = rty.str.getSingle match case One(tname) => - val value = AbsValue(tname match - case "Object" => RecordT("Object") - case "Symbol" => SymbolT - case "Number" => NumberT - case "BigInt" => BigIntT - case "String" => StrT - case "Boolean" => BoolT - case "Undefined" => UndefT - case "Null" => NullT - case _ => ValueTy(), - ) + val value = AbsValue(ValueTy.fromTypeOf(tname)) if (positive) lv ⊓ value else lv -- value case _ => lv _ <- modify(_.update(l, refinedV)) diff --git a/src/main/scala/esmeta/ty/ListTy.scala b/src/main/scala/esmeta/ty/ListTy.scala index 705c8afd93..e73c4abab9 100644 --- a/src/main/scala/esmeta/ty/ListTy.scala +++ b/src/main/scala/esmeta/ty/ListTy.scala @@ -63,6 +63,11 @@ enum ListTy extends TyElem with Lattice[ListTy] { def normalized: ListTy = this match case Elem(elem) if elem.isTop => Top case _ => this + + /** mapping function */ + def map(f: ValueTy => ValueTy): ListTy = this match + case Elem(elem) => Elem(f(elem)) + case _ => this } object ListTy extends Parser.From(Parser.listTy) { def apply(elem: ValueTy): ListTy = Elem(elem).normalized diff --git a/src/main/scala/esmeta/ty/ValueTy.scala b/src/main/scala/esmeta/ty/ValueTy.scala index e074af3741..5729e5bcfd 100644 --- a/src/main/scala/esmeta/ty/ValueTy.scala +++ b/src/main/scala/esmeta/ty/ValueTy.scala @@ -377,4 +377,16 @@ object ValueTy extends Parser.From(Parser.valueTy) { ).norm lazy val Top: ValueTy = ValueTopTy lazy val Bot: ValueTy = ValueElemTy() + + /** get type from `typeof` string */ + def fromTypeOf(str: String): ValueTy = str match + case "Object" => RecordT("Object") + case "Symbol" => SymbolT + case "Number" => NumberT + case "BigInt" => BigIntT + case "String" => StrT + case "Boolean" => BoolT + case "Undefined" => UndefT + case "Null" => NullT + case _ => Bot } diff --git a/src/main/scala/esmeta/ty/package.scala b/src/main/scala/esmeta/ty/package.scala index 914576be92..2ea0ef6ea5 100644 --- a/src/main/scala/esmeta/ty/package.scala +++ b/src/main/scala/esmeta/ty/package.scala @@ -30,7 +30,7 @@ lazy val AbruptT: ValueTy = ValueTy(record = RecordTy("AbruptCompletion")) def AbruptT(xs: String*): ValueTy = AbruptT(xs.toSet) def AbruptT(xs: Set[String]): ValueTy = ValueTy(record = RecordTy("AbruptCompletion", Map("Type" -> EnumT(xs.toSet)))) -lazy val ThrowT: ValueTy = AbruptT("throw") +lazy val ThrowT: ValueTy = RecordT("ThrowCompletion") lazy val NormalT: ValueTy = ValueTy(record = RecordTy("NormalCompletion")) def NormalT(value: ValueTy): ValueTy = if (value.isBottom) BotT diff --git a/src/main/scala/esmeta/ty/util/Parser.scala b/src/main/scala/esmeta/ty/util/Parser.scala index cf0d4601e7..cfe1ea8c1b 100644 --- a/src/main/scala/esmeta/ty/util/Parser.scala +++ b/src/main/scala/esmeta/ty/util/Parser.scala @@ -80,13 +80,17 @@ trait Parsers extends BasicParsers { "Bot" ^^^ BotT | // completion record "Completion" ^^^ CompT | - "Normal" ~> opt("[" ~> valueTy <~ "]") ^^ { - case None => NormalT - case Some(v) => NormalT(v) - } | "Abrupt" ~> opt("[" ~> rep1sep(ident, ",") <~ "]") ^^ { - case None => AbruptT - case Some(names) => AbruptT(names.toSet) - } | + "Normal" ~> opt( + "[" ~> valueTy <~ "]" ^^ { NormalT(_) } | + fieldMap ^^ { RecordT("NormalCompletion", _) }, + ) ^^ { _.getOrElse(NormalT) } | + "Abrupt" ~> opt( + "[" ~> rep1sep(ident, ",") <~ "]" ^^ { xs => AbruptT(xs.toSet) } | + fieldMap ^^ { RecordT("AbruptCompletion", _) }, + ) ^^ { _.getOrElse(AbruptT) } | + "Throw" ~> opt( + fieldMap ^^ { RecordT("ThrowCompletion", _) }, + ) ^^ { _.getOrElse(ThrowT) } | // ECMAScript value "ESValue" ^^^ ESValueT | // closure diff --git a/src/main/scala/esmeta/ty/util/Stringifier.scala b/src/main/scala/esmeta/ty/util/Stringifier.scala index 21a6ecf16e..cb12dbf1c8 100644 --- a/src/main/scala/esmeta/ty/util/Stringifier.scala +++ b/src/main/scala/esmeta/ty/util/Stringifier.scala @@ -184,6 +184,11 @@ object Stringifier { if (fm.map.keySet == Set("Type")) app >> fm("Type").value.enumv else if (!fm.isTop) app >> " " >> fm } + map.get("ThrowCompletion").map { fm => + m -= "ThrowCompletion" + mayOR >> "Throw" + if (!fm.isTop) app >> " " >> fm + } if (m.nonEmpty) if (prevExists) app >> OR app >> "Record[" >> m.toList.sortBy(_._1) >> "]"