From 3d66e7beea454cb3324047ffe30e6ffa76d0cb6a Mon Sep 17 00:00:00 2001 From: ivan-klass Date: Wed, 20 Dec 2023 12:31:16 +0100 Subject: [PATCH] Use full names in macro to avoid collision - fixes #3407 (#3413) --- .../sttp/tapir/internal/OneOfMacro.scala | 44 +++++++------------ .../internal/SchemaAnnotationsMacro.scala | 2 +- .../sttp/tapir/internal/SchemaMapMacro.scala | 6 +-- .../internal/ValidatorEnumerationMacro.scala | 4 +- .../SchemaMacroNamespaceTest.scala | 7 +++ .../BuiltinTypenameCollisionCaseClass.scala | 26 +++++++++++ .../BuiltinTypenameCollisionEnum.scala | 24 ++++++++++ 7 files changed, 80 insertions(+), 33 deletions(-) create mode 100644 core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionCaseClass.scala create mode 100644 core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionEnum.scala diff --git a/core/src/main/scala-2/sttp/tapir/internal/OneOfMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/OneOfMacro.scala index a662315471..72f9f60ba6 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/OneOfMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/OneOfMacro.scala @@ -45,31 +45,26 @@ private[tapir] object OneOfMacro { val schemaForE = q"""{ - import _root_.sttp.tapir.internal._ - import _root_.sttp.tapir.Schema - import _root_.sttp.tapir.Schema._ - import _root_.sttp.tapir.SchemaType._ - import _root_.scala.collection.immutable.{List, Map} - val mappingAsList = List(..$mapping) - val mappingAsMap: Map[$weakTypeV, Schema[_]] = mappingAsList.toMap + val mappingAsList = _root_.scala.collection.immutable.List(..$mapping) + val mappingAsMap: _root_.scala.collection.immutable.Map[$weakTypeV, _root_.sttp.tapir.Schema[_]] = mappingAsList.toMap val discriminatorName = _root_.sttp.tapir.FieldName($name, $conf.toEncodedName($name)) // cannot use .collect because of a bug in ScalaJS (Trying to access the this of another class ... during phase: jscode) val discriminatorMapping = mappingAsMap.toList.flatMap { - case (k, Schema(_, Some(fname), _, _, _, _, _, _, _, _, _)) => List($asString.apply(k) -> SRef(fname)) - case _ => Nil + case (k, _root_.sttp.tapir.Schema(_, _root_.scala.Some(fname), _, _, _, _, _, _, _, _, _)) => _root_.scala.collection.immutable.List($asString.apply(k) -> _root_.sttp.tapir.SchemaType.SRef(fname)) + case _ => _root_.scala.Nil } .toMap - val sname = SName(${weakTypeE.typeSymbol.fullName},${extractTypeArguments(c)(weakTypeE)}) + val sname = _root_.sttp.tapir.Schema.SName(${weakTypeE.typeSymbol.fullName},${extractTypeArguments(c)(weakTypeE)}) // cast needed because of Scala 2.12 val subtypes = mappingAsList.map(_._2) - Schema((SCoproduct[$weakTypeE](subtypes, None) { e => + _root_.sttp.tapir.Schema((_root_.sttp.tapir.SchemaType.SCoproduct[$weakTypeE](subtypes, _root_.scala.None) { e => val ee: $weakTypeV = $extractor(e) - mappingAsMap.get(ee).map(m => SchemaWithValue(m.asInstanceOf[Schema[Any]], e)) + mappingAsMap.get(ee).map(m => _root_.sttp.tapir.SchemaType.SchemaWithValue(m.asInstanceOf[_root_.sttp.tapir.Schema[Any]], e)) }).addDiscriminatorField( discriminatorName, $discriminatorSchema, discriminatorMapping - ), Some(sname)) + ), _root_.scala.Some(sname)) }""" Debug.logGeneratedCode(c)(weakTypeE.typeSymbol.fullName, schemaForE) @@ -88,32 +83,27 @@ private[tapir] object OneOfMacro { val subclasses = symbol.knownDirectSubclasses.toList.sortBy(_.name.encodedName.toString) val subclassesSchemas = subclasses.map(subclass => - q"${subclass.name.encodedName.toString} -> Schema.wrapWithSingleFieldProduct(implicitly[Schema[${subclass.asType}]])($conf)" + q"${subclass.name.encodedName.toString} -> _root_.sttp.tapir.Schema.wrapWithSingleFieldProduct(_root_.scala.Predef.implicitly[_root_.sttp.tapir.Schema[${subclass.asType}]])($conf)" ) val subclassesSchemaCases = subclasses.map { subclass => - cq"""v: ${subclass.asType} => Some(SchemaWithValue(subclassNameToSchemaMap(${subclass.name.encodedName.toString}).asInstanceOf[Schema[Any]], v))""" + cq"""v: ${subclass.asType} => _root_.scala.Some(_root_.sttp.tapir.SchemaType.SchemaWithValue(subclassNameToSchemaMap(${subclass.name.encodedName.toString}).asInstanceOf[_root_.sttp.tapir.Schema[Any]], v))""" } val schemaForE = q"""{ - import _root_.sttp.tapir.Schema - import _root_.sttp.tapir.Schema._ - import _root_.sttp.tapir.SchemaType._ - import _root_.scala.collection.immutable.{List, Map} + val subclassNameToSchema: _root_.scala.collection.immutable.List[(String, _root_.sttp.tapir.Schema[_])] = _root_.scala.collection.immutable.List($subclassesSchemas: _*) + val subclassNameToSchemaMap: _root_.scala.collection.immutable.Map[String, _root_.sttp.tapir.Schema[_]] = subclassNameToSchema.toMap - val subclassNameToSchema: List[(String, Schema[_])] = List($subclassesSchemas: _*) - val subclassNameToSchemaMap: Map[String, Schema[_]] = subclassNameToSchema.toMap - - val sname = SName(${weakTypeE.typeSymbol.fullName},${extractTypeArguments(c)(weakTypeE)}) + val sname = _root_.sttp.tapir.Schema.SName(${weakTypeE.typeSymbol.fullName},${extractTypeArguments(c)(weakTypeE)}) // cast needed because of Scala 2.12 val subtypes = subclassNameToSchema.map(_._2) - Schema( - schemaType = SCoproduct[$weakTypeE](subtypes, None) { e => + _root_.sttp.tapir.Schema( + schemaType = _root_.sttp.tapir.SchemaType.SCoproduct[$weakTypeE](subtypes, _root_.scala.None) { e => e match { case ..$subclassesSchemaCases } - }, - name = Some(sname) + }, + name = _root_.scala.Some(sname) ) }""" diff --git a/core/src/main/scala-2/sttp/tapir/internal/SchemaAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/SchemaAnnotationsMacro.scala index 3142efb3ee..ccad712f9e 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/SchemaAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/SchemaAnnotationsMacro.scala @@ -43,7 +43,7 @@ private[tapir] object SchemaAnnotationsMacro { val validateEach = annotations.collect { case ann if ann.tree.tpe <:< ValidateEachAnn => firstArg(ann) } c.Expr[SchemaAnnotations[T]]( - q"""_root_.sttp.tapir.SchemaAnnotations.apply($description, $encodedExample, $default, $format, $deprecated, $hidden, $encodedName, List(..$validate), List(..$validateEach))""" + q"""_root_.sttp.tapir.SchemaAnnotations.apply($description, $encodedExample, $default, $format, $deprecated, $hidden, $encodedName, _root_.scala.List(..$validate), _root_.scala.List(..$validateEach))""" ) } } diff --git a/core/src/main/scala-2/sttp/tapir/internal/SchemaMapMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/SchemaMapMacro.scala index 657fb95966..31cb69003f 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/SchemaMapMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/SchemaMapMacro.scala @@ -9,7 +9,7 @@ private[tapir] object SchemaMapMacro { c: blackbox.Context )(schemaForV: c.Expr[Schema[V]]): c.Expr[Schema[Map[String, V]]] = { import c.universe._ - generateSchemaForMap[String, V](c)(c.Expr[String => String](q"""identity"""))(schemaForV) + generateSchemaForMap[String, V](c)(c.Expr[String => String](q"""_root_.scala.Predef.identity"""))(schemaForV) } /* Extract name and generic type parameters of map value type for object info creation */ @@ -34,8 +34,8 @@ private[tapir] object SchemaMapMacro { q"""{ val s = $schemaForV _root_.sttp.tapir.Schema( - _root_.sttp.tapir.SchemaType.SOpenProduct(Nil, s)(_.map { case (k, v) => ($keyToString(k), v) }), - Some(_root_.sttp.tapir.Schema.SName("Map", $genericTypeParameters)) + _root_.sttp.tapir.SchemaType.SOpenProduct(_root_.scala.Nil, s)(_.map { case (k, v) => ($keyToString(k), v) }), + _root_.scala.Some(_root_.sttp.tapir.Schema.SName("Map", $genericTypeParameters)) ) }""" Debug.logGeneratedCode(c)(weakTypeV.typeSymbol.fullName, schemaForMap) diff --git a/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala index bea98f7989..336b96dae6 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala @@ -29,7 +29,7 @@ private[tapir] object ValidatorEnumerationMacro { } else { val instances = subclasses.map(x => Ident(x.asInstanceOf[scala.reflect.internal.Symbols#Symbol].sourceModule.asInstanceOf[Symbol])) val validatorEnum = - q"_root_.sttp.tapir.Validator.Enumeration($instances, None, Some(_root_.sttp.tapir.Schema.SName(${symbol.fullName})))" + q"_root_.sttp.tapir.Validator.Enumeration($instances, _root_.scala.None, _root_.scala.Some(_root_.sttp.tapir.Schema.SName(${symbol.fullName})))" Debug.logGeneratedCode(c)(t.typeSymbol.fullName, validatorEnum) c.Expr[Validator.Enumeration[E]](validatorEnum) } @@ -54,7 +54,7 @@ private[tapir] object ValidatorEnumerationMacro { case Nil => c.abort(c.enclosingPosition, s"Invalid enum name: ${weakTypeT.toString}") } - q"_root_.sttp.tapir.Validator.enumeration($enumeration.values.toList, v => Option(v), Some(sttp.tapir.Schema.SName(${enumNameComponents + q"_root_.sttp.tapir.Validator.enumeration($enumeration.values.toList, v => _root_.scala.Option(v), _root_.scala.Some(sttp.tapir.Schema.SName(${enumNameComponents .mkString(".")})))" } } diff --git a/core/src/test/scala/sttp/tapir/namespacing/SchemaMacroNamespaceTest.scala b/core/src/test/scala/sttp/tapir/namespacing/SchemaMacroNamespaceTest.scala index 8b00a7906b..9218f85462 100644 --- a/core/src/test/scala/sttp/tapir/namespacing/SchemaMacroNamespaceTest.scala +++ b/core/src/test/scala/sttp/tapir/namespacing/SchemaMacroNamespaceTest.scala @@ -16,4 +16,11 @@ class SchemaMacroNamespaceTest extends AnyFlatSpec with Matchers { import sttp.tapir.Codec Codec.derivedEnumeration[String, MyProduct](MyProduct.fromString, _.toString) } + + it should "compile macro-generated code for shadowed _root_.scala names" in { + + assert(sttp.tapir.testdata.BuiltinTypenameCollisionEnum.schema.name.nonEmpty) + assert(sttp.tapir.testdata.BuiltinTypenameCollisionCaseClass.schema.name.nonEmpty) + } + } diff --git a/core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionCaseClass.scala b/core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionCaseClass.scala new file mode 100644 index 0000000000..e4f748edfa --- /dev/null +++ b/core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionCaseClass.scala @@ -0,0 +1,26 @@ +package sttp.tapir.testdata + +sealed abstract class BuiltinTypenameCollisionCaseClass + +object BuiltinTypenameCollisionCaseClass { + + case class Option(a: String) extends BuiltinTypenameCollisionCaseClass + case class Some(a: Int) extends BuiltinTypenameCollisionCaseClass + case class None(b: Boolean) extends BuiltinTypenameCollisionCaseClass + case class List(c: String) extends BuiltinTypenameCollisionCaseClass + case class Nil(b: String) extends BuiltinTypenameCollisionCaseClass + case class Map(d: Int) extends BuiltinTypenameCollisionCaseClass + // TODO: magnolia issue - https://github.com/softwaremill/magnolia/pull/504 + // case class Array(a: Boolean) + case class Either(e: String) extends BuiltinTypenameCollisionCaseClass + case class Left(x: Double) extends BuiltinTypenameCollisionCaseClass + case class Right(y: Double) extends BuiltinTypenameCollisionCaseClass + case class Unit() extends BuiltinTypenameCollisionCaseClass + case class implicitly(a: Int) extends BuiltinTypenameCollisionCaseClass + case class identity(b: Boolean) extends BuiltinTypenameCollisionCaseClass + + import sttp.tapir.generic.auto._ + + val schema: sttp.tapir.Schema[BuiltinTypenameCollisionCaseClass] = + sttp.tapir.Schema.oneOfWrapped[BuiltinTypenameCollisionCaseClass] +} diff --git a/core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionEnum.scala b/core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionEnum.scala new file mode 100644 index 0000000000..be0fbb4faf --- /dev/null +++ b/core/src/test/scala/sttp/tapir/testdata/BuiltinTypenameCollisionEnum.scala @@ -0,0 +1,24 @@ +package sttp.tapir.testdata + +/** @see [[https://github.com/softwaremill/tapir/issues/3407 Github Issue #3407]] */ +sealed abstract class BuiltinTypenameCollisionEnum + +object BuiltinTypenameCollisionEnum { + case object Option extends BuiltinTypenameCollisionEnum + case object Some extends BuiltinTypenameCollisionEnum + case object None extends BuiltinTypenameCollisionEnum + case object List extends BuiltinTypenameCollisionEnum + case object Nil extends BuiltinTypenameCollisionEnum + case object Map extends BuiltinTypenameCollisionEnum + case object Array extends BuiltinTypenameCollisionEnum + case object Either extends BuiltinTypenameCollisionEnum + case object Left extends BuiltinTypenameCollisionEnum + case object Right extends BuiltinTypenameCollisionEnum + case object Unit extends BuiltinTypenameCollisionEnum + + case object implicitly extends BuiltinTypenameCollisionEnum + case object identity extends BuiltinTypenameCollisionEnum + + val schema: sttp.tapir.Schema[BuiltinTypenameCollisionEnum] = + sttp.tapir.Schema.derivedEnumeration[BuiltinTypenameCollisionEnum].defaultStringBased +}