Skip to content

Commit

Permalink
Merge pull request #7 from mblink/higher-order-function-types
Browse files Browse the repository at this point in the history
Higher order function types
  • Loading branch information
jleider authored Mar 15, 2021
2 parents 128eff7 + c8c1fc6 commit 34398cf
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 16 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
.idea
.vscode
*.log

# Scala generated files
*.class
target

# Metals generated files
metals.sbt
.bloop
.metals
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")

resolvers += "bondlink-maven-repo" at "https://raw.githubusercontent.com/mblink/maven-repo/main"
addSbtPlugin("bondlink" % "sbt-git-publish" % "0.0.4")
addSbtPlugin("bondlink" % "sbt-git-publish" % "0.0.5")
1 change: 1 addition & 0 deletions src/main/scala/com/mpc/scalats/configuration/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ case class Config(
)

case class TsImports(
fptsOption: String = "fp-ts/lib/Option",
fptsEither: String = "fp-ts/lib/Either",
fptsThese: String = "fp-ts/lib/These",
fptsPipe: (String, String) = ("pipe", "fp-ts/lib/function"),
Expand Down
5 changes: 2 additions & 3 deletions src/main/scala/com/mpc/scalats/core/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.mpc.scalats.core.TypeScriptModel.{
SingletonDeclaration
}
import scala.collection.immutable.ListSet
import scala.reflect.runtime.universe.typeOf

case class Compiler(config: Config) {
@inline def compile(
Expand Down Expand Up @@ -139,8 +138,7 @@ case class Compiler(config: Config) {
TypeScriptModel.SimpleTypeRef(name)

case ScalaModel.OptionRef(innerType) =>
TypeScriptModel.CustomTypeRef("optionFromNullable", ListSet(
compileTypeRef(innerType, inInterfaceContext)), typeOf[Option[_]].typeConstructor)
TypeScriptModel.OptionType(compileTypeRef(innerType, inInterfaceContext))

case ScalaModel.MapRef(kT, vT) => TypeScriptModel.MapType(
compileTypeRef(kT, inInterfaceContext),
Expand Down Expand Up @@ -175,6 +173,7 @@ case class Compiler(config: Config) {
case TypeScriptModel.UnknownTypeRef(n) => n == d.name
case TypeScriptModel.SimpleTypeRef(n) => n == d.name
case TypeScriptModel.UnionType(_, rs, _) => rs.exists(refersToRef(_, d))
case TypeScriptModel.OptionType(i) => refersToRef(i, d)
case TypeScriptModel.EitherType(l, r) => refersToRef(l, d) || refersToRef(r, d)
case TypeScriptModel.TheseType(l, r) => refersToRef(l, d) || refersToRef(r, d)
case TypeScriptModel.MapType(k, v) => refersToRef(k, d) || refersToRef(v, d)
Expand Down
7 changes: 5 additions & 2 deletions src/main/scala/com/mpc/scalats/core/Emitter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,20 @@ trait Emitter extends TsImports.HelperSyntax {
case StringRef => "string"
case DateRef => imports.iotsLocalDate
case DateTimeRef => "Date"
case ArrayRef(innerType) => getTypeRefString(innerType) |+| "[]"
case ArrayRef(innerType) => "ReadonlyArray<" |+| getTypeRefString(innerType) |+| ">"
case NonEmptyArrayRef(innerType) => imports.iotsReadonlyNonEmptyArray.value |+| "<" |+| getTypeRefString(innerType) |+| ">"
case CustomTypeRef(name, params, scalaType) =>
if (params.isEmpty) imports.custom(scalaType, name)
else name |+| params.joinTypeParams(getTypeRefString)
else imports.custom(scalaType, name) |+| params.joinTypeParams(getTypeRefString)
case UnknownTypeRef(typeName) => typeName
case SimpleTypeRef(param) => param

case UnionType(name, possibilities, scalaType) =>
imports.custom(scalaType, name).orElse(possibilities.joinParens(" | ")(getTypeRefString))

case OptionType(iT) =>
imports.fptsOption("Option") |+| List(iT).joinTypeParams(getTypeRefString)

case EitherType(lT, rT) =>
imports.fptsEither("Either") |+| List(lT, rT).joinTypeParams(getTypeRefString)

Expand Down
35 changes: 26 additions & 9 deletions src/main/scala/com/mpc/scalats/core/IoTsEmitter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,40 @@ final class IoTsEmitter(val config: Config) extends Emitter {
emitTypeDeclaration(decl)
}

private def typeParamArgs(typeParams: List[String]): TsImports.With[String] =
private def iotsTypeParamArgs(typeParams: List[String]): TsImports.With[String] =
if (typeParams.isEmpty) (imports.empty, "") else
typeParams.joinTypeParams(p => s"$p extends " |+| imports.iotsMixed) |+|
typeParams.joinParens(", ")(p => s"${typeAsValArg(p)}: $p") |+| " => "

private def interfaceTypeParamArgs(typeParams: List[String]): TsImports.With[String] =
if (typeParams.isEmpty) (imports.empty, "") else typeParams.joinTypeParams(p => p)

def emitIoTsInterfaceDeclaration(decl: InterfaceDeclaration)(implicit ctx: TsImports.Ctx): Lines = {
val InterfaceDeclaration(name, fields, typeParams, _) = decl
val InterfaceDeclaration(name, fields, typeParams, superInterface) = decl
val typeVal = codecName(name)
val typeType = codecType(name)

imports.iotsTypeFunction.lines(
s => s"export const $typeVal = " |+| typeParamArgs(list(typeParams)) |+| s"$s{",
s => s"export const $typeVal = " |+| iotsTypeParamArgs(list(typeParams)) |+| s"$s{",
fields.joinLines(",")(v => s"${indent}${v.name}: " |+| getIoTsTypeString(v.typeRef)),
"}" ++ _ ++ ";"
) |+|
(if (typeParams.isEmpty) line(s"export type $name = " |+| imports.iotsTypeOf |+| s"<typeof $typeVal>;") else Nil) |+|
(
if (typeParams.isEmpty) {
line(s"export type $typeType = typeof $typeVal;")
} else {
line() |+|
emitInterfaceDeclaration(name, fields, typeParams, superInterface) |+|
line(s"export type $typeType" |+| typeParams.joinTypeParams(t => s"$t extends " |+| imports.iotsTypeTypeC |+| "<any>") |+| s" = ${interfaceName(name)}<" |+| imports.iotsTypeOf |+| s"<${typeParams.mkString(", ")}>>;")
}
) |+|
(if (typeParams.isEmpty) line(s"export type $name = " |+| imports.iotsTypeOf |+| s"<$typeType>;") else Nil) |+|
List("")
}

def emitInterfaceDeclaration(name: String, members: ListSet[Member], superInterface: Option[InterfaceDeclaration])(implicit ctx: TsImports.Ctx): Lines =
def emitInterfaceDeclaration(name: String, members: ListSet[Member], typeParams: ListSet[String], superInterface: Option[InterfaceDeclaration])(implicit ctx: TsImports.Ctx): Lines =
if (!config.emitInterfaces) Nil else
line(s"export interface ${interfaceName(name)}${superInterface.fold("")(i => s" extends ${i.name}")} {") |+|
line(s"export interface ${interfaceName(name)}" |+| interfaceTypeParamArgs(list(typeParams)) |+| s"${superInterface.fold("")(i => s" extends ${i.name}")} {") |+|
emitMembers(members, true, false) |+|
line("}") |+|
line()
Expand All @@ -70,7 +83,7 @@ final class IoTsEmitter(val config: Config) extends Emitter {
(if (emitAll) line(s"export const all$name = " |+| union(objectName, "[", ", ", "]") |+| " as const;") else Nil) |+|
line(s"export type ${name}U = " |+| imports.iotsTypeOf |+| s"<typeof ${tsUnionName(name)}>;") |+|
line() |+|
emitInterfaceDeclaration(name, members, superInterface)
emitInterfaceDeclaration(name, members, ListSet.empty, superInterface)
}

private def isAbstractMember(member: Member, superInterface: Option[InterfaceDeclaration]): Boolean = {
Expand Down Expand Up @@ -133,7 +146,7 @@ final class IoTsEmitter(val config: Config) extends Emitter {
val TypeDeclaration(name, typeRef, typeParams) = decl
val typeVal = codecName(name)

line(s"export const $typeVal = " |+| typeParamArgs(list(typeParams)) |+| getIoTsTypeString(typeRef) |+| ";") |+|
line(s"export const $typeVal = " |+| iotsTypeParamArgs(list(typeParams)) |+| getIoTsTypeString(typeRef) |+| ";") |+|
(if (typeParams.isEmpty) line(s"export type $name = " |+| imports.iotsTypeOf |+| s"<typeof $typeVal>;") else Nil ) |+|
line()
}
Expand All @@ -159,6 +172,8 @@ final class IoTsEmitter(val config: Config) extends Emitter {
case SimpleTypeRef(param) => (imports.empty, typeAsValArg(param))
case UnionType(name, possibilities, scalaType) =>
imports.custom(scalaType, tsUnionName(name)).orElse(imports.iotsUnion(possibilities.joinArray(getIoTsTypeString)))
case OptionType(iT) =>
imports.iotsOption(getIoTsTypeString(iT))
case EitherType(lT, rT) =>
imports.iotsEither(List(lT, rT).join(", ")(getIoTsTypeString))
case TheseType(lT, rT) =>
Expand Down Expand Up @@ -192,6 +207,8 @@ final class IoTsEmitter(val config: Config) extends Emitter {
case UnionType(name, possibilities, scalaType) =>
imports.custom(scalaType, name).orElse(imports.iotsStrict(
imports.iotsUnion(possibilities.joinArray(getIoTsTypeWrappedVal(value, _)))))
case OptionType(iT) =>
imports.iotsOption(getIoTsTypeWrappedVal(value, iT))
case EitherType(lT, rT) =>
imports.iotsStrict(imports.iotsUnion(
List(lT, rT).joinArray(getIoTsTypeWrappedVal(value, _))))
Expand All @@ -210,7 +227,6 @@ final class IoTsEmitter(val config: Config) extends Emitter {

def customIoTsTypes(scalaType: Type, name: String, customName: String => String)(implicit ctx: TsImports.Ctx): TsImports.With[String] = name match {
case "NonEmptyList" => imports.iotsReadonlyNonEmptyArray.value
case "optionFromNullable" => imports.iotsOption.value
case _ => imports.custom(scalaType, customName(name))
}

Expand All @@ -234,6 +250,7 @@ final class IoTsEmitter(val config: Config) extends Emitter {
case CustomTypeRef(n, _, t) if t =:= valueType => (n, t)
}.getOrElse(sys.error(s"Value $value of type $valueType is not a member of union $u"))
imports.custom(customType, objectName(customTypeName))
case OptionType(iT) => getIoTsTypeString(iT)
case EitherType(lT, rT) => imports.iotsUnion(List(lT, rT).joinArray(getIoTsTypeString))
case TheseType(lT, rT) => imports.iotsUnion(List(lT, rT, TupleType(ListSet(lT, rT))).joinArray(getIoTsTypeString))
case MapType(keyType, valueType) => imports.iotsRecord(List(keyType, valueType).join(", ")(getIoTsTypeString))
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/com/mpc/scalats/core/TsImports.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ object TsImports {
private def optImport(o: Option[(String, String)], tpeName: String, cfgKey: String): With[String] =
o.map(namedImport).getOrElse(sys.error(s"$tpeName type requested but $cfgKey import config value missing"))

lazy val fptsOption = CallableImport(all(tsi.fptsOption, "O"), "O", ".", "")
lazy val fptsEither = CallableImport(all(tsi.fptsEither, "E"), "E", ".", "")
lazy val fptsPipe = CallableImport(namedImport(tsi.fptsPipe))
lazy val fptsThese = CallableImport(all(tsi.fptsThese, "Th"), "Th", ".", "")
Expand All @@ -164,6 +165,7 @@ object TsImports {
lazy val iotsTuple = CallableImport(iotsImport, "t.tuple")
lazy val iotsTypeFunction = CallableImport(iotsImport, "t.type")
lazy val iotsTypeType = (iotsImport, "t.Type")
lazy val iotsTypeTypeC = (iotsImport, "t.TypeC")
lazy val iotsTypeOf = (iotsImport, "t.TypeOf")
lazy val iotsUndefined = (iotsImport, "t.undefined")
lazy val iotsUnion = CallableImport(iotsImport, "t.union")
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/com/mpc/scalats/core/TypeScriptModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ object TypeScriptModel {

case class UnionType(name: String, possibilities: ListSet[TypeRef], scalaType: Type) extends TypeRef

case class OptionType(t: TypeRef) extends TypeRef

case class EitherType(lT: TypeRef, rT: TypeRef) extends TypeRef

case class TheseType(lT: TypeRef, rT: TypeRef) extends TypeRef
Expand Down
2 changes: 1 addition & 1 deletion version.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version in ThisBuild := "0.7.2-BL"
version in ThisBuild := "0.7.3-BL-SNAPSHOT"

0 comments on commit 34398cf

Please sign in to comment.