From ea716a40d04c671892c6a9c548afa86c57c309fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Raddum=20Berg?= Date: Tue, 18 Oct 2022 00:50:14 +0200 Subject: [PATCH] Guard type expansion against `StackOverflowError`. It's all kinds of bad, but it's better to finish the conversion with slightly worse results than to fail it. Fixes #412 --- .../ts/transforms/ExpandTypeMappings.scala | 163 ++++++++++-------- 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/ts/src/main/scala/org/scalablytyped/converter/internal/ts/transforms/ExpandTypeMappings.scala b/ts/src/main/scala/org/scalablytyped/converter/internal/ts/transforms/ExpandTypeMappings.scala index 96334a406c..edfbca3638 100644 --- a/ts/src/main/scala/org/scalablytyped/converter/internal/ts/transforms/ExpandTypeMappings.scala +++ b/ts/src/main/scala/org/scalablytyped/converter/internal/ts/transforms/ExpandTypeMappings.scala @@ -13,96 +13,107 @@ object ExpandTypeMappings extends TreeTransformationScopedChanges { x.copy(parts = IArray(x.parts.last)) } - override def enterTsType(scope: TsTreeScope)(x: TsType): TsType = { - lazy val nameHint = TsTypeFormatter.dropComments(Unqualify.visitTsType(())(x)).filter(_.isLetterOrDigit) - - def repeated = - scope.stack.exists { - case TsTypeObject(Comments(cs), _) => - cs.exists { - case Marker.NameHint(`nameHint`) => true - case _ => false + override def enterTsType(scope: TsTreeScope)(x: TsType): TsType = + try { + lazy val nameHint = TsTypeFormatter.dropComments(Unqualify.visitTsType(())(x)).filter(_.isLetterOrDigit) + + def repeated = + scope.stack.exists { + case TsTypeObject(Comments(cs), _) => + cs.exists { + case Marker.NameHint(`nameHint`) => true + case _ => false + } + case _ => false + } + + def refersAbstract = + TsTreeTraverse + .collect(x) { + case TsTypeRef(_, name @ TsQIdent(IArray.exactlyOne(_)), Empty) if scope.isAbstract(name) => name } - case _ => false - } + .nonEmpty - def refersAbstract = - TsTreeTraverse - .collect(x) { - case TsTypeRef(_, name @ TsQIdent(IArray.exactlyOne(_)), Empty) if scope.isAbstract(name) => name + def tooDeep = { + val numTypeObjects = scope.stack.count { + case TsTypeObject(_, _) => true; + case _ => false } - .nonEmpty - - def tooDeep = { - val numTypeObjects = scope.stack.count { - case TsTypeObject(_, _) => true; - case _ => false + numTypeObjects > 2 } - numTypeObjects > 2 - } - - if (repeated || refersAbstract || tooDeep) return x - - AllMembersFor.forType(scope, LoopDetector.initial)(x) match { - case Problems(_) => - x - case Ok(newMembers, true) => - val nameHint = TsTypeFormatter.dropComments(Unqualify.visitTsType(())(x)).filter(_.isLetterOrDigit) + if (repeated || refersAbstract || tooDeep) return x - val notices = Comments( - List( - Comment("/* Inlined " + TsTypeFormatter(x) + " */\n"), - Marker.NameHint(nameHint), - ), - ) - TsTypeObject(notices, newMembers) - case _ => x - } - } - } - - override def enterTsDecl(scope: TsTreeScope)(x: TsDecl): TsDecl = - x match { - case i: TsDeclInterface => - AllMembersFor.forInterface(scope, LoopDetector.initial)(i) match { + AllMembersFor.forType(scope, LoopDetector.initial)(x) match { case Problems(_) => - i + x + case Ok(newMembers, true) => + val nameHint = TsTypeFormatter.dropComments(Unqualify.visitTsType(())(x)).filter(_.isLetterOrDigit) + val notices = Comments( - i.inheritance.map(i => Comment("/* Inlined parent " + TsTypeFormatter(i) + " */\n")).toList, - ) - i.copy( - comments = i.comments ++ notices, - members = newMembers, - inheritance = Empty, + List( + Comment("/* Inlined " + TsTypeFormatter(x) + " */\n"), + Marker.NameHint(nameHint), + ), ) + TsTypeObject(notices, newMembers) case _ => x } + } catch { + case _: StackOverflowError => + scope.logger.warn(s"SOE while expanding ${TsTypeFormatter(x)}") + x // todo: all of `ExpandTypeMappings` should be much improvd + } + } - case ta @ TsDeclTypeAlias(comments, declared, name, tparams, alias, codePath) - if !comments.has[Marker.IsTrivial.type] && !pointsToConcreteType(scope, alias) => - AllMembersFor.forType(scope, LoopDetector.initial)(alias) match { - case Problems(_) => - evaluateKeys(scope, LoopDetector.initial)(alias) match { - case Ok(value, true) => - val notice = Comment("/* Inlined " + TsTypeFormatter(alias) + " */\n") - ta.copy( - comments = comments + notice, - alias = TsTypeUnion.simplified(IArray.fromTraversable(value).map(x => TsTypeLiteral(x.lit))), - ) - case Ok(_, false) => - ta - case Problems(_) => - ta - } - case Ok(newMembers, true) => - val notice = Comment("/* Inlined " + TsTypeFormatter(alias) + " */\n") - TsDeclInterface(comments + notice, declared, name, tparams, Empty, newMembers, codePath) - case _ => x - } + override def enterTsDecl(scope: TsTreeScope)(x: TsDecl): TsDecl = + try { + x match { + case i: TsDeclInterface => + AllMembersFor.forInterface(scope, LoopDetector.initial)(i) match { + case Problems(_) => + i + case Ok(newMembers, true) => + val notices = Comments( + i.inheritance.map(i => Comment("/* Inlined parent " + TsTypeFormatter(i) + " */\n")).toList, + ) + i.copy( + comments = i.comments ++ notices, + members = newMembers, + inheritance = Empty, + ) + case _ => x + } - case _ => x + case ta @ TsDeclTypeAlias(comments, declared, name, tparams, alias, codePath) + if !comments.has[Marker.IsTrivial.type] && !pointsToConcreteType(scope, alias) => + AllMembersFor.forType(scope, LoopDetector.initial)(alias) match { + case Problems(_) => + evaluateKeys(scope, LoopDetector.initial)(alias) match { + case Ok(value, true) => + val notice = Comment("/* Inlined " + TsTypeFormatter(alias) + " */\n") + ta.copy( + comments = comments + notice, + alias = TsTypeUnion.simplified(IArray.fromTraversable(value).map(x => TsTypeLiteral(x.lit))), + ) + case Ok(_, false) => + ta + case Problems(_) => + ta + } + case Ok(newMembers, true) => + val notice = Comment("/* Inlined " + TsTypeFormatter(alias) + " */\n") + TsDeclInterface(comments + notice, declared, name, tparams, Empty, newMembers, codePath) + case _ => x + } + + case _ => x + } + } catch { + case _: StackOverflowError => + scope.logger.warn(s"SOE while expanding ") + x // todo: all of `ExpandTypeMappings` should be much improvd } val EmptySet = Set.empty[String]