diff --git a/sdk/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/PackageUploader.scala b/sdk/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/PackageUploader.scala index 35e948b2a94d..2dbc2e80dff4 100644 --- a/sdk/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/PackageUploader.scala +++ b/sdk/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/PackageUploader.scala @@ -196,7 +196,7 @@ class PackageUploader( EitherT.fromEither[FutureUnlessShutdown]( engine.validateDar(dar).left.flatMap { case err: EngineError.Package.DarSelfConsistency - if (!enableStrictDarValidation || dar.main._2.languageVersion < LV.Features.explicitPkgImports) && err.logReportingEnabled => + if (!enableStrictDarValidation || LV.featurePackageImports.versionReq.contains(dar.main._2.languageVersion)) && err.logReportingEnabled => logger.warn(err.message) Right(()) case err: EngineError.Package.Error => diff --git a/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV2.scala b/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV2.scala index 36a465a902bf..7c21aac9b801 100644 --- a/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV2.scala +++ b/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV2.scala @@ -192,7 +192,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { val lfKinds = lfPackage.getInternedKindsList if (!lfKinds.isEmpty) - assertSince(LV.Features.kindInterning, "interned kinds table") + assertVersionSupports(LV.featureFlatArchive) lfKinds.iterator.asScala .foldLeft(new mutable.ArrayBuffer[Kind](lfKinds.size)) { (buf, kind) => @@ -221,7 +221,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { Left("PLF.Package.ImportsSumCase.IMPORTSSUM_NOT_SET") case PLF.Package.ImportsSumCase.PACKAGE_IMPORTS => val imports = lfPackage.getPackageImports.getImportedPackagesList.asScala.toIndexedSeq - if (languageVersion < LV.Features.explicitPkgImports) + if (!versionSupports(LV.featurePackageImports)) throw Error.Parsing( s"Explicit pkg imports set on unsupported lf version (version ${languageVersion}), case set PACKAGE_IMPORTS, to ${imports}" ) @@ -229,7 +229,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { Right(imports) case PLF.Package.ImportsSumCase.NO_IMPORTED_PACKAGES_REASON => val reason = lfPackage.getNoImportedPackagesReason - if (languageVersion < LV.Features.explicitPkgImports) + if (!versionSupports(LV.featurePackageImports)) throw Error.Parsing( s"Explicit pkg imports set on unsupported lf version (version ${languageVersion}), case NO_IMPORTED_PACKAGES_REASON, set to ${reason}" ) @@ -554,7 +554,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { tpl: DottedName, key: PLF.DefTemplate.DefKey, ): Work[TemplateKey] = { - assertSince(LV.Features.contractKeys, "key") + assertVersionSupports(LV.featureContractKeys) decodeExpr(key.getKeyExpr, s"${tpl}:key") { keyExpr => decodeType(key.getType) { typ => decodeExpr(key.getMaintainers, s"${tpl}:maintainer") { maintainers => @@ -583,7 +583,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { Work.sequence(lfImplements.view.map(decodeTemplateImplements(_))) { implements => Work.bind( if (lfTempl.hasKey) { - assertSince(LV.Features.contractKeys, "key") + assertVersionSupports(LV.featureContractKeys) Work.bind(decodeTemplateKey(tpl, lfTempl.getKey)) { tk => Ret(Some(tk)) } @@ -660,12 +660,11 @@ private[archive] class DecodeV2(minor: LV.Minor) { ) { choiceObservers => Work.bind( if (lfChoice.hasAuthorizers) { - assertSince(LV.Features.choiceAuthority, "TemplateChoice.authorizers") decodeExpr(lfChoice.getAuthorizers, s"$tpl:$chName:authorizers") { authorizers => Ret(Some(authorizers)) } } else { - // authorizers are optional post LV.Features.choiceAuthority + // authorizers are optional post LV.featureChoiceAuthority Ret(None) } ) { choiceAuthorizers => @@ -757,7 +756,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { } } case PLF.Kind.SumCase.INTERNED_KIND => - assertSince(LV.Features.kindInterning, "interned kinds unsupported in this version") + assertVersionSupports(LV.featureFlatArchive, Some("interned kinds")) Ret( internedKinds.applyOrElse( lfKind.getInternedKind, @@ -834,7 +833,10 @@ private[archive] class DecodeV2(minor: LV.Minor) { case PLF.Type.SumCase.BUILTIN => val builtin = lfType.getBuiltin val info = builtinTypeInfoMap(builtin.getBuiltin) - assertSince(info.minVersion, builtin.getBuiltin.getValueDescriptor.getFullName) + assertVersionInRange( + info.versionReq, + Some(builtin.getBuiltin.getValueDescriptor.getFullName), + ) val baseType: Type = info.typ val args = builtin.getArgsList.asScala assertNullIfSupportsFlatArchive(args, "builtin.getArgsList") @@ -876,7 +878,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { ) ) case PLF.Type.SumCase.TAPP => - if (!(languageVersion >= LV.Features.flatArchive)) + if (!versionSupports(LV.featureFlatArchive)) throw Error.Parsing( s"Illegal case: TApp not supported in version ${languageVersion}, since this version has local flattening" ) @@ -900,7 +902,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { this.packageId case SC.IMPORTED_PACKAGE_ID_INTERNED_STR => val id = getInternedPackageId(lfId.getPackageId.getImportedPackageIdInternedStr) - if (!stableIds.contains(id) && languageVersion >= LV.Features.explicitPkgImports) + if (!stableIds.contains(id) && versionSupports(LV.featurePackageImports)) throw Error.Parsing( s"Got explicit non-stable package id (${id}) on lf version (${languageVersion}) that supports explicit package imports, imports: (${imports})" ) @@ -982,8 +984,10 @@ private[archive] class DecodeV2(minor: LV.Minor) { case PLF.Expr.SumCase.BUILTIN => val info = builtinInfoMap(lfExpr.getBuiltin) - assertSince(info.minVersion, lfExpr.getBuiltin.getValueDescriptor.getFullName) - info.maxVersion.foreach(assertUntil(_, lfExpr.getBuiltin.getValueDescriptor.getFullName)) + assertVersionInRange( + info.versionReq, + Some(lfExpr.getBuiltin.getValueDescriptor.getFullName), + ) Ret(info.expr) case PLF.Expr.SumCase.REC_CON => @@ -1247,7 +1251,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { } case PLF.Expr.SumCase.UNSAFE_FROM_INTERFACE => - assertUntil(LV.Features.unsafeFromInterfaceRemoved, "Expr.unsafe_from_interface") + assertVersionSupports(LV.featureUnsafeFromInterface, Some("Expr.unsafe_from_interface")) val unsafeFromInterface = lfExpr.getUnsafeFromInterface val interfaceId = decodeTypeConId(unsafeFromInterface.getInterfaceType) val templateId = decodeTypeConId(unsafeFromInterface.getTemplateType) @@ -1313,7 +1317,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { } case PLF.Expr.SumCase.CHOICE_CONTROLLER => - assertSince(LV.Features.choiceFuncs, "Expr.choice_controller") + assertVersionSupports(LV.featureChoiceFuncs, Some("Expr.choice_controller")) val choiceController = lfExpr.getChoiceController val tplCon = decodeTypeConId(choiceController.getTemplate) val choiceName = internedName(choiceController.getChoiceInternedStr) @@ -1324,7 +1328,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { } case PLF.Expr.SumCase.CHOICE_OBSERVER => - assertSince(LV.Features.choiceFuncs, "Expr.choice_observer") + assertVersionSupports(LV.featureChoiceFuncs, Some("Expr.choice_observer")) val choiceObserver = lfExpr.getChoiceObserver val tplCon = decodeTypeConId(choiceObserver.getTemplate) val choiceName = internedName(choiceObserver.getChoiceInternedStr) @@ -1335,14 +1339,14 @@ private[archive] class DecodeV2(minor: LV.Minor) { } case PLF.Expr.SumCase.EXPERIMENTAL => - assertSince(LV.Features.unstable, "Expr.experimental") + assertVersionSupports(LV.featureUnstable) val experimental = lfExpr.getExperimental decodeType(experimental.getType) { typ => Ret(EExperimental(experimental.getName, typ)) } case PLF.Expr.SumCase.INTERNED_EXPR => - assertSince(LV.Features.exprInterning, "interned exprs unsupported in this version") + assertVersionSupports(LV.featureFlatArchive, Some("INTERNED_EXPR")) val exprIdx = lfExpr.getInternedExpr if (currentInternedExprId.exists(_ <= exprIdx)) throw Error.IllegalInterning( @@ -1412,7 +1416,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { } private[this] def decodeRetrieveByKey(value: PLF.Update.RetrieveByKey): Work[TypeConId] = { - assertSince(LV.Features.contractKeys, "RetrieveByKey") + assertVersionSupports(LV.featureContractKeys, Some("RetrieveByKey")) Ret(decodeTypeConId(value.getTemplate)) } @@ -1465,7 +1469,10 @@ private[archive] class DecodeV2(minor: LV.Minor) { decodeExpr(exercise.getArg, definition) { argE => Work.bind( if (exercise.hasGuard) { - assertSince(LV.Features.extendedInterfaces, "exerciseInterface.guard") + assertVersionSupports( + LV.featureExtendedInterfaces, + Some("exerciseInterface.guard"), + ) decodeExpr(exercise.getGuard, definition) { e => Ret(Some(e)) } @@ -1480,7 +1487,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { } case PLF.Update.SumCase.EXERCISE_BY_KEY => - assertSince(LV.Features.contractKeys, "exercise_by_key") + assertVersionSupports(LV.featureContractKeys, Some("exercise_by_key")) val exerciseByKey = lfUpdate.getExerciseByKey val templateId = decodeTypeConId(exerciseByKey.getTemplate) val choice = getInternedName(exerciseByKey.getChoiceInternedStr) @@ -1514,13 +1521,13 @@ private[archive] class DecodeV2(minor: LV.Minor) { } case PLF.Update.SumCase.FETCH_BY_KEY => - assertSince(LV.Features.contractKeys, "fetch_by_key") + assertVersionSupports(LV.featureContractKeys, Some("fetch_by_key")) Work.bind(decodeRetrieveByKey(lfUpdate.getFetchByKey)) { tmplId => Ret(UpdateFetchByKey(tmplId)) } case PLF.Update.SumCase.LOOKUP_BY_KEY => - assertSince(LV.Features.contractKeys, "lookup_by_key") + assertVersionSupports(LV.featureContractKeys, Some("lookup_by_key")) Work.bind(decodeRetrieveByKey(lfUpdate.getLookupByKey)) { tmplId => Ret(UpdateLookupByKey(tmplId)) } @@ -1603,7 +1610,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { case PLF.BuiltinLit.SumCase.NUMERIC_INTERNED_STR => toBLNumeric(getInternedStr(lfBuiltinLit.getNumericInternedStr)) case PLF.BuiltinLit.SumCase.ROUNDING_MODE => - assertSince(LV.Features.bigNumeric, "Expr.rounding_mode") + assertVersionSupports(LV.featureBigNumeric, Some("Expr.rounding_mode")) BLRoundingMode(java.math.RoundingMode.valueOf(lfBuiltinLit.getRoundingModeValue)) case PLF.BuiltinLit.SumCase.FAILURE_CATEGORY => BLFailureCategory(lfBuiltinLit.getFailureCategory match { @@ -1619,8 +1626,31 @@ private[archive] class DecodeV2(minor: LV.Minor) { } } - private def versionIsOlderThan(minVersion: LV): Boolean = - languageVersion < minVersion + private def versionSupports(ft: LV.Feature): Boolean = + ft.versionReq.contains(languageVersion) + + def assertVersionSupports(ft: LV.Feature, caseDescription: Option[String] = None): Unit = { + if (!versionSupports(ft)) { + val optDescr = caseDescription match { + case Some(str) => s" (case ${str})" + case None => "" + } + throw notSupportedError(ft.name ++ optDescr) + } + } + + private def versionInRange(r: VersionRange[LV]): Boolean = + r.contains(languageVersion) + + def assertVersionInRange(r: VersionRange[LV], caseDescription: Option[String] = None): Unit = { + if (!versionInRange(r)) { + val optDescr = caseDescription match { + case Some(str) => s" (case ${str})" + case None => "" + } + throw notSupportedError(s"${languageVersion} not in range ${r}" ++ optDescr) + } + } private[this] def toName(s: String): Name = eitherToParseError(Name.fromString(s)) @@ -1639,16 +1669,6 @@ private[archive] class DecodeV2(minor: LV.Minor) { private[this] def notSupportedError(description: String): Error.Parsing = Error.Parsing(s"$description is not supported by Daml-LF 2.${minor.pretty}") - // maxVersion excluded - private[this] def assertUntil(maxVersion: LV, description: => String): Unit = - if (!versionIsOlderThan(maxVersion)) - throw notSupportedError(description) - - // minVersion included - private[this] def assertSince(minVersion: LV, description: => String): Unit = - if (versionIsOlderThan(minVersion)) - throw notSupportedError(description) - private def assertNonEmpty(s: collection.Seq[_], description: => String): Unit = if (s.isEmpty) throw Error.Parsing(s"Unexpected empty $description") @@ -1656,7 +1676,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { s: collection.Seq[_], description: => String, ): Unit = - if ((!versionIsOlderThan(LV.Features.flatArchive)) && s.length != 1) + if (versionSupports(LV.featureFlatArchive) && s.length != 1) throw Error.Parsing( s"Illegal local flattening: since lf flattening is supported, expected a single element for $description, but found ${s.length}, version ${languageVersion}" ) @@ -1665,7 +1685,7 @@ private[archive] class DecodeV2(minor: LV.Minor) { s: collection.Seq[_], description: => String, ): Unit = - if ((!versionIsOlderThan(LV.Features.flatArchive)) && s.length != 0) + if (versionSupports(LV.featureFlatArchive) && s.length != 0) throw Error.Parsing( s"Illegal local flattening: Since lf flattening is supported, expected a null list $description, but found ${s.length}, version ${languageVersion}" ) @@ -1683,10 +1703,12 @@ private[lf] object DecodeV2 { private def eitherToParseError[A](x: Either[String, A]): A = x.fold(err => throw Error.Parsing(err), identity) + // TODO https://github.com/digital-asset/daml/issues/22365 + // migrate to versionReq case class BuiltinTypeInfo( proto: PLF.BuiltinType, bTyp: BuiltinType, - minVersion: LV = LV.Features.default, + versionReq: VersionRange[LV] = LV.allLfVersionsRange, ) { val typ = TBuiltin(bTyp) } @@ -1711,8 +1733,16 @@ private[lf] object DecodeV2 { BuiltinTypeInfo(NUMERIC, BTNumeric), BuiltinTypeInfo(ANY, BTAny), BuiltinTypeInfo(TYPE_REP, BTTypeRep), - BuiltinTypeInfo(BIGNUMERIC, BTBigNumeric, minVersion = LV.Features.bigNumeric), - BuiltinTypeInfo(ROUNDING_MODE, BTRoundingMode, minVersion = LV.Features.bigNumeric), + BuiltinTypeInfo( + BIGNUMERIC, + BTBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinTypeInfo( + ROUNDING_MODE, + BTRoundingMode, + versionReq = LV.featureBigNumeric.versionReq, + ), BuiltinTypeInfo(ANY_EXCEPTION, BTAnyException), BuiltinTypeInfo(FAILURE_CATEGORY, BTFailureCategory), ) @@ -1723,18 +1753,18 @@ private[lf] object DecodeV2 { .map(info => info.proto -> info) .toMap + // TODO https://github.com/digital-asset/daml/issues/22365 + // migrate to versionReq case class BuiltinFunctionInfo( proto: PLF.BuiltinFunction, builtin: BuiltinFunction, - minVersion: LV = LV.Features.default, // first version that does support the builtin - maxVersion: Option[LV] = None, // first version that does not support the builtin + versionReq: VersionRange[LV] = LV.allLfVersionsRange, implicitParameters: List[Type] = List.empty, ) { val expr: Expr = implicitParameters.foldLeft[Expr](EBuiltinFun(builtin))(ETyApp) } val builtinFunctionInfos: List[BuiltinFunctionInfo] = { - import LV.Features._ import PLF.BuiltinFunction._ List( BuiltinFunctionInfo(ADD_NUMERIC, BAddNumeric), @@ -1801,18 +1831,62 @@ private[lf] object DecodeV2 { BuiltinFunctionInfo(EQUAL_LIST, BEqualList), BuiltinFunctionInfo(TRACE, BTrace), BuiltinFunctionInfo(COERCE_CONTRACT_ID, BCoerceContractId), - BuiltinFunctionInfo(SCALE_BIGNUMERIC, BScaleBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(PRECISION_BIGNUMERIC, BPrecisionBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(ADD_BIGNUMERIC, BAddBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(SUB_BIGNUMERIC, BSubBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(MUL_BIGNUMERIC, BMulBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(DIV_BIGNUMERIC, BDivBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(SHIFT_RIGHT_BIGNUMERIC, BShiftRightBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(BIGNUMERIC_TO_NUMERIC, BBigNumericToNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(NUMERIC_TO_BIGNUMERIC, BNumericToBigNumeric, minVersion = bigNumeric), - BuiltinFunctionInfo(BIGNUMERIC_TO_TEXT, BBigNumericToText, minVersion = bigNumeric), + BuiltinFunctionInfo( + SCALE_BIGNUMERIC, + BScaleBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + PRECISION_BIGNUMERIC, + BPrecisionBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + ADD_BIGNUMERIC, + BAddBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + SUB_BIGNUMERIC, + BSubBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + MUL_BIGNUMERIC, + BMulBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + DIV_BIGNUMERIC, + BDivBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + SHIFT_RIGHT_BIGNUMERIC, + BShiftRightBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + BIGNUMERIC_TO_NUMERIC, + BBigNumericToNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + NUMERIC_TO_BIGNUMERIC, + BNumericToBigNumeric, + versionReq = LV.featureBigNumeric.versionReq, + ), + BuiltinFunctionInfo( + BIGNUMERIC_TO_TEXT, + BBigNumericToText, + versionReq = LV.featureBigNumeric.versionReq, + ), BuiltinFunctionInfo(ANY_EXCEPTION_MESSAGE, BAnyExceptionMessage), - BuiltinFunctionInfo(TYPE_REP_TYCON_NAME, BTypeRepTyConName, minVersion = unstable), + BuiltinFunctionInfo( + TYPE_REP_TYCON_NAME, + BTypeRepTyConName, + versionReq = LV.featureUnstable.versionReq, + ), BuiltinFunctionInfo(FAIL_WITH_STATUS, BFailWithStatus), ) } diff --git a/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/package.scala b/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/package.scala index f1dcdb1e0438..4297f1e4990a 100644 --- a/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/package.scala +++ b/sdk/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/package.scala @@ -72,7 +72,7 @@ package object archive { getClass.getCanonicalName + ".Lf2PackageParser", { cos => val langVersion = LanguageVersion(LanguageVersion.Major.V2, minor) - if (langVersion < LanguageVersion.Features.flatArchive) + if (!LanguageVersion.featureFlatArchive.enabledIn(langVersion)) discard(cos.setRecursionLimit(EXTENDED_PROTOBUF_RECURSION_LIMIT)) DamlLf2.Package.parseFrom(cos) }, @@ -84,7 +84,7 @@ package object archive { GenReader( getClass.getCanonicalName + ".ModuleParser", { cos => - if (ver < LanguageVersion.Features.flatArchive) + if (!LanguageVersion.featureFlatArchive.enabledIn(ver)) discard(cos.setRecursionLimit(EXTENDED_PROTOBUF_RECURSION_LIMIT)) DamlLf2.Package.parseFrom(cos) }, diff --git a/sdk/daml-lf/archive/src/test/scala/com/digitalasset/daml/lf/archive/DecodeV2Spec.scala b/sdk/daml-lf/archive/src/test/scala/com/digitalasset/daml/lf/archive/DecodeV2Spec.scala index 9eb863152b08..9140e84a2e1f 100644 --- a/sdk/daml-lf/archive/src/test/scala/com/digitalasset/daml/lf/archive/DecodeV2Spec.scala +++ b/sdk/daml-lf/archive/src/test/scala/com/digitalasset/daml/lf/archive/DecodeV2Spec.scala @@ -130,7 +130,7 @@ class DecodeV2Spec .setInternedKind(32) .build() - forEveryVersionSuchThat(_ < LV.Features.kindInterning) { version => + forEveryVersionSuchThat(!LV.featureFlatArchive.versionReq.contains(_)) { version => an[Error.Parsing] shouldBe thrownBy(moduleDecoder(version).decodeKindForTest(input)) } } @@ -150,7 +150,7 @@ class DecodeV2Spec } } - s"Reject local flattening if lf version >= ${LV.Features.exprInterning}" in { + s"Reject local flattening if lf version in ${LV.featureFlatArchive}" in { val input = DamlLf2.Kind .newBuilder() .setArrow( @@ -162,7 +162,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ >= LV.Features.kindInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => inside(Try(moduleDecoder(version).decodeKindForTest(input))) { case Failure(Error.Parsing(message)) => message should include("Illegal local flattening") @@ -180,7 +180,7 @@ class DecodeV2Spec .addInternedKinds(kstar) .build() - forEveryVersionSuchThat(_ < LV.Features.kindInterning) { version => + forEveryVersionSuchThat(!LV.featureFlatArchive.versionReq.contains(_)) { version => val decoder = new DecodeV2(version.minor) // TODO: https://github.com/digital-asset/daml/issues/21155 // convert to proper error catching @@ -198,7 +198,7 @@ class DecodeV2Spec .addInternedKinds(kind) .build() - forEveryVersionSuchThat(_ >= LV.Features.kindInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -255,7 +255,7 @@ class DecodeV2Spec ) "translate TNumeric as is" in { - forEveryVersionSuchThat(_ < LV.Features.flatArchive) { version => + forEveryVersionSuchThat(!LV.featureFlatArchive.versionReq.contains(_)) { version => val decoder = moduleDecoder(version) forEvery(numericTestCases) { (input, expectedOutput) => decoder.uncheckedDecodeTypeForTest(input) shouldBe expectedOutput @@ -270,8 +270,8 @@ class DecodeV2Spec } } - s"reject BigNumeric and RoundingMode if version < ${LV.Features.bigNumeric}" in { - forEveryVersionSuchThat(_ < LV.Features.bigNumeric) { version => + s"reject BigNumeric and RoundingMode if version < ${LV.featureBigNumeric}" in { + forEveryVersionSuchThat(!LV.featureBigNumeric.versionReq.contains(_)) { version => val decoder = moduleDecoder(version) an[Error.Parsing] shouldBe thrownBy( decoder.uncheckedDecodeTypeForTest(buildPrimType(BIGNUMERIC)) @@ -282,8 +282,8 @@ class DecodeV2Spec } } - s"accept BigNumeric and RoundingMode if version >= ${LV.Features.bigNumeric}" in { - forEveryVersionSuchThat(_ >= LV.Features.bigNumeric) { version => + s"accept BigNumeric and RoundingMode if version >= ${LV.featureBigNumeric}" in { + forEveryVersionSuchThat(LV.featureBigNumeric.versionReq.contains) { version => val decoder = moduleDecoder(version) decoder.uncheckedDecodeTypeForTest(buildPrimType(BIGNUMERIC)) shouldBe TBigNumeric decoder.uncheckedDecodeTypeForTest(buildPrimType(ROUNDING_MODE)) shouldBe TRoundingMode @@ -340,7 +340,7 @@ class DecodeV2Spec } } - s"Reject local flattening if lf version >= ${LV.Features.exprInterning}" should { + s"Reject local flattening if lf version in ${LV.featureFlatArchive}" should { "for case VAR (enforced: null)" in { val stringTable = ImmArraySeq("a") @@ -354,7 +354,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ >= LV.Features.kindInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => inside(Try(moduleDecoder(version, stringTable).uncheckedDecodeTypeForTest(input))) { case Failure(Error.Parsing(message)) => message should include("Illegal local flattening") @@ -381,7 +381,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ >= LV.Features.kindInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => inside( Try( moduleDecoder(version, stringTable, dottedNameTable).uncheckedDecodeTypeForTest(input) @@ -402,7 +402,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ >= LV.Features.kindInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => inside(Try(moduleDecoder(version).uncheckedDecodeTypeForTest(input))) { case Failure(Error.Parsing(message)) => message should include("Illegal local flattening") @@ -429,7 +429,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ >= LV.Features.kindInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => inside(Try(moduleDecoder(version, stringTable).uncheckedDecodeTypeForTest(input))) { case Failure(Error.Parsing(message)) => message should include("Illegal local flattening") @@ -640,7 +640,7 @@ class DecodeV2Spec } } - s"translate BigNumeric builtins iff version >= ${LV.Features.bigNumeric}" in { + s"translate BigNumeric builtins iff version in ${LV.featureBigNumeric}" in { val exceptionBuiltinCases = Table( "exception builtins" -> "expected output", DamlLf2.BuiltinFunction.SCALE_BIGNUMERIC -> @@ -666,7 +666,7 @@ class DecodeV2Spec forEvery(exceptionBuiltinCases) { (proto, scala) => val result = Try(decoder.decodeExprForTest(toProtoExpr(proto), "test")) - if (version >= LV.Features.bigNumeric) + if (LV.featureBigNumeric.versionReq.contains(version)) result shouldBe Success(scala) else inside(result) { case Failure(error) => error shouldBe an[Error.Parsing] } @@ -692,14 +692,14 @@ class DecodeV2Spec .setBuiltinLit(DamlLf2.BuiltinLit.newBuilder().setRoundingMode(s)) .build() - s"translate RoundingMode iff version >= ${LV.Features.bigNumeric}" in { + s"translate RoundingMode iff version >= ${LV.featureBigNumeric}" in { forEveryVersion { version => val decoder = moduleDecoder(version) forEvery(roundingModeTestCases) { (proto, scala) => val result = Try(decoder.decodeExprForTest(roundingToProtoExpr(proto), "test")) - if (version >= LV.Features.bigNumeric) + if (LV.featureBigNumeric.versionReq.contains(version)) result shouldBe Success(Ast.EBuiltinLit(Ast.BLRoundingMode(scala))) else inside(result) { case Failure(error) => error shouldBe an[Error.Parsing] } @@ -961,12 +961,14 @@ class DecodeV2Spec forEveryVersion { version => val result = Try(interfacePrimitivesDecoder(version).decodeExprForTest(unsafeFromInterface, "test")) - if (version < LV.Features.unsafeFromInterfaceRemoved) result shouldBe Success(expected) + if (LV.featureUnsafeFromInterface.versionReq.contains(version)) + // featureUnsafeFromInterface is a removed feature + result shouldBe Success(expected) else inside(result) { case Failure(error) => error shouldBe an[Error.Parsing] } } } - s"decode extended TypeRep iff version < ${LV.Features.templateTypeRepToText}" in { + s"decode extended TypeRep iff version not in ${LV.featureTemplateTypeRepToText}" in { val testCases = { val typeRepTyConName = DamlLf2.Expr .newBuilder() @@ -984,7 +986,7 @@ class DecodeV2Spec forEveryVersion { version => forEvery(testCases) { (proto, scala) => val result = Try(interfacePrimitivesDecoder(version).decodeExprForTest(proto, "test")) - if (version < LV.Features.templateTypeRepToText) + if (!LV.featureTemplateTypeRepToText.versionReq.contains(version)) inside(result) { case Failure(error) => error shouldBe a[Error.Parsing] } else result shouldBe Success(scala) @@ -1057,7 +1059,7 @@ class DecodeV2Spec } } - s"translate interface exercise guard iff version >= ${LV.Features.extendedInterfaces}" in { + s"translate interface exercise guard iff version in ${LV.featureExtendedInterfaces}" in { val pkgRef = DamlLf2.SelfOrImportedPackageId.newBuilder().setSelfPackageId(unit).build val modRef = @@ -1085,7 +1087,7 @@ class DecodeV2Spec Some(EUnit), ) - forEveryVersionSuchThat(_ >= LV.Features.extendedInterfaces) { version => + forEveryVersionSuchThat(LV.featureExtendedInterfaces.versionReq.contains) { version => val decoder = moduleDecoder( version, @@ -1105,7 +1107,7 @@ class DecodeV2Spec .setInternedExpr(0) .build() - forEveryVersionSuchThat(_ < LV.Features.exprInterning) { version => + forEveryVersionSuchThat(!LV.featureFlatArchive.versionReq.contains(_)) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -1137,7 +1139,7 @@ class DecodeV2Spec .setInternedExpr(0) .build() - forEveryVersionSuchThat(_ >= LV.Features.exprInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -1158,7 +1160,7 @@ class DecodeV2Spec .setInternedExpr(1) .build() - forEveryVersionSuchThat(_ >= LV.Features.exprInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -1193,7 +1195,7 @@ class DecodeV2Spec .setAbs(ab) .build() - forEveryVersionSuchThat(_ >= LV.Features.exprInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -1205,7 +1207,7 @@ class DecodeV2Spec } } - s"Reject local flattening if lf version >= ${LV.Features.exprInterning}" should { + s"Reject local flattening if lf version in ${LV.featureFlatArchive}" should { "for case ABS (enforced: singleton)" in { val internedZero = DamlLf2.Expr .newBuilder() @@ -1235,7 +1237,7 @@ class DecodeV2Spec .setAbs(ab) .build() - forEveryVersionSuchThat(_ >= LV.Features.exprInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -1271,7 +1273,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ >= LV.Features.exprInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -1311,7 +1313,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ >= LV.Features.exprInterning) { version => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { version => val decoder = new DecodeV2(version.minor) val env = decoder.Env( packageId = Ref.PackageId.assertFromString("noPkgId"), @@ -1789,7 +1791,7 @@ class DecodeV2Spec } "gracefully fail when expression too deep when version supports expression interning" in { - forEveryVersionSuchThat(_ >= LV.Features.flatArchive) { _ => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { _ => inside(Decode.decodeArchive(exprToArch(buildLet(500), "dev"))) { case Left(err) => err shouldBe an[Error.IO] } @@ -1797,7 +1799,7 @@ class DecodeV2Spec } "not fail when expression deep but not too deep when version supports expression interning" in { - forEveryVersionSuchThat(_ >= LV.Features.flatArchive) { _ => + forEveryVersionSuchThat(LV.featureFlatArchive.versionReq.contains) { _ => // explanation for "magic" number: // // The amount of nested lets is not equal to the proto limit since there @@ -1810,7 +1812,7 @@ class DecodeV2Spec } "still accept reasonably deep expressions when version does not support" in { - forEveryVersionSuchThat(_ < LV.Features.flatArchive) { _ => + forEveryVersionSuchThat(!LV.featureFlatArchive.versionReq.contains(_)) { _ => // explanation for "magic" number: see above Decode.decodeArchive(exprToArch(buildLet(498), "1")) shouldBe a[Right[_, _]] } @@ -1909,7 +1911,7 @@ class DecodeV2Spec } } - s"reject experiment expression if LF version < ${LV.Features.unstable}" in { + s"reject experiment expression if LF version not in ${LV.featureUnstable}" in { val expr = DamlLf2.Expr .newBuilder() @@ -1927,7 +1929,7 @@ class DecodeV2Spec ) .build() - forEveryVersionSuchThat(_ < LV.Features.unstable) { version => + forEveryVersionSuchThat(!LV.featureUnstable.versionReq.contains(_)) { version => val decoder = moduleDecoder(version) an[Error.Parsing] shouldBe thrownBy(decoder.decodeExprForTest(expr, "test")) } diff --git a/sdk/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/VersionRange.scala b/sdk/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/VersionRange.scala index bf4ac8000125..bf6ed1a80a1f 100644 --- a/sdk/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/VersionRange.scala +++ b/sdk/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/VersionRange.scala @@ -3,27 +3,87 @@ package com.digitalasset.daml.lf -import scala.Ordering.Implicits.infixOrderingOps - -/** [[VersionRange]] represents a range of versions of +/** [[VersionRange]] represents a range of versions any type but usually of * [[com.digitalasset.daml.lf.LanguageVersion]] or * [[com.digitalasset.daml.lf.SerializationVersion]]. * - * @param min the minimal version included in the range. - * @param max the maximal version included in the range. + * This can represent an empty range, a one-sided range (from/until), or + * an inclusive two-sided range. + * * @tparam V either [[com.digitalasset.daml.lf.LanguageVersion]] or - * [[com.digitalasset.daml.lf.SerializationVersion]]. + * [[com.digitalasset.daml.lf.SerializationVersion]]. */ -final case class VersionRange[V]( - min: V, - max: V, -)(implicit ordering: Ordering[V]) { +sealed abstract class VersionRange[V](implicit val ordering: Ordering[V]) + extends Product + with Serializable { + + def pretty: String + + /** The minimum version of this range, if bounded below. */ + def minOption: Option[V] + + /** The maximum version of this range, if bounded above. */ + def maxOption: Option[V] + + def contains(v: V): Boolean = this match { + case VersionRange.Empty() => false + case VersionRange.From(lowerBound) => ordering.gteq(v, lowerBound) + case VersionRange.Until(upperBound) => ordering.lteq(v, upperBound) + case VersionRange.Inclusive(lowerBound, upperBound) => + ordering.gteq(v, lowerBound) && ordering.lteq(v, upperBound) + } + + def map[W](f: V => W)(implicit ordering: Ordering[W]): VersionRange[W] = this match { + case VersionRange.Empty() => VersionRange.Empty[W]() + case VersionRange.From(lowerBound) => VersionRange.From(f(lowerBound)) + case VersionRange.Until(upperBound) => VersionRange.Until(f(upperBound)) + case VersionRange.Inclusive(lowerBound, upperBound) => + VersionRange.Inclusive(f(lowerBound), f(upperBound)) + } +} + +object VersionRange { + + /** The default `apply` constructor creates an inclusive range, + * preserving the behavior of the old case class (which only supported inclusive ranges). + */ + def apply[V: Ordering](min: V, max: V): VersionRange.Inclusive[V] = + Inclusive(min, max) + + final case class Empty[V: Ordering]() extends VersionRange[V] { + def pretty: String = "[] (empty range)" + override def minOption: Option[V] = None + override def maxOption: Option[V] = None + } + + /** Represents a one-sided range `[min..]`. */ + final case class From[V: Ordering](min: V) extends VersionRange[V] { + def pretty: String = s"[${min}..] (all from ${min} upwards)" + override def minOption: Option[V] = Some(min) + override def maxOption: Option[V] = None + } + + /** Represents a one-sided range `[..max]`. */ + final case class Until[V: Ordering](max: V) extends VersionRange[V] { + def pretty: String = s"[..${max}] (all up to and including ${max})" + override def minOption: Option[V] = None + override def maxOption: Option[V] = Some(max) + } + + /** Represents an inclusive range `[min, max]`. This is the default */ + final case class Inclusive[V: Ordering](min: V, max: V) extends VersionRange[V] { + require( + ordering.lteq(min, max), + s"Invalid ordering, min (${min}) was not <= max (${max})", + ) - require(min <= max, s"Invalid ordering, min (${min}) was not <= max (${max})") + def pretty: String = s"[${min}..${max}]" - private[lf] def map[W](f: V => W)(implicit ordering: Ordering[W]) = - VersionRange(f(min), f(max)) + override def minOption: Option[V] = Some(min) + override def maxOption: Option[V] = Some(max) - def contains(v: V): Boolean = - min <= v && v <= max + // override map to get a map from inclusive to inclusive + override def map[W](f: V => W)(implicit ordering: Ordering[W]): VersionRange.Inclusive[W] = + VersionRange.Inclusive(f(min), f(max)) + } } diff --git a/sdk/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV2.scala b/sdk/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV2.scala index 89ffbae77933..26432b4d2547 100644 --- a/sdk/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV2.scala +++ b/sdk/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV2.scala @@ -103,7 +103,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { dottedNameTable.build.foreach(builder.addInternedDottedNames) stringsTable.build.foreach(builder.addInternedStrings) - if (languageVersion >= LV.Features.explicitPkgImports) { + if (versionSupports(LV.featureFlatArchive)) { pkg.imports match { case DeclaredImports(importsSet) => builder.setPackageImports(encodeImports(importsSet)) case GeneratedImports(str, _) => @@ -171,7 +171,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { else { val builder = PLF.SelfOrImportedPackageId.newBuilder() - if (!stableIds.contains(pkgId) && languageVersion >= LV.Features.explicitPkgImports) + if (!stableIds.contains(pkgId) && versionSupports(LV.featurePackageImports)) pkgImports match { case Right(ids) => builder.setPackageImportId(ids(pkgId)) case Left(rsn) => sys.error(s"in encodePackageId, did not find imports, reason {$rsn}") @@ -252,7 +252,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { }) private implicit def encodeKind(k: Kind): PLF.Kind = - if (languageVersion >= LV.Features.kindInterning) + if (versionSupports(LV.featureFlatArchive)) PLF.Kind.newBuilder().setInternedKind(kindTable.insert(k)).build() else encodeKindBuilder(k).build() @@ -260,7 +260,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { val builder = PLF.Kind.newBuilder() // KArrows breaks the exhaustiveness checker. (k: @unchecked) match { - case KArrows(params, result) if languageVersion < LV.Features.flatArchive => + case KArrows(params, result) if !(versionSupports(LV.featureFlatArchive)) => expect(result == KStar) builder .setArrow( @@ -324,13 +324,11 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { private def encodeTypeBuilder(typ0: Type): PLF.Type.Builder = { val (typ, args) = typ0 match { - case TApps(typ1, args1) if languageVersion < LV.Features.flatArchive => typ1 -> args1 + case TApps(typ1, args1) if !(versionSupports(LV.featureFlatArchive)) => typ1 -> args1 case _ => typ0 -> ImmArray.Empty } val builder = PLF.Type.newBuilder() - // Be warned: Both the use of the unapply pattern TForalls and the pattern - // case TBuiltin(BTArrow) if versionIsOlderThan(LV.Features.arrowType) => - // cause scala's exhaustivty checking to be disabled in the following match. + // Be warned: Both the use of the unapply pattern TForalls cause scala's exhaustivty checking to be disabled in the following match. (typ: @unchecked) match { case TVar(varName) => val b = PLF.Type.Var.newBuilder() @@ -350,7 +348,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { PLF.Type.Builtin.newBuilder().setBuiltin(proto).accumulateLeft(typs)(_ addArgs _) ) case TApp(lhs, rhs) => - if (languageVersion >= LV.Features.flatArchive) + if (versionSupports(LV.featureFlatArchive)) builder.setTapp( PLF.Type.TApp .newBuilder() @@ -360,12 +358,12 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { else sys.error(s"unexpected TApp on lf version $languageVersion") // TODO[RB]: expect args empty??? - case TForalls(binders, body) if languageVersion < LV.Features.flatArchive => + case TForalls(binders, body) if !(versionSupports(LV.featureFlatArchive)) => expect(args.isEmpty) builder.setForall( PLF.Type.Forall.newBuilder().accumulateLeft(binders)(_ addVars _).setBody(body) ) - case TForall(binder, body) if languageVersion >= LV.Features.flatArchive => + case TForall(binder, body) if (versionSupports(LV.featureFlatArchive)) => builder.setForall( PLF.Type.Forall.newBuilder().addVars(binder).setBody(body) ) @@ -447,7 +445,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { } private implicit def encodeExpr(expr: Expr): PLF.Expr = - if (languageVersion >= LV.Features.exprInterning) + if (versionSupports(LV.featureFlatArchive)) PLF.Expr.newBuilder().setInternedExpr(exprTable.insert(expr)).build() else encodeExprBuilder(expr).build() @@ -497,7 +495,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { b.setCid(cid) b.setArg(arg) guard.foreach { g => - assertSince(LV.Features.extendedInterfaces, "ExerciseInterface.guard") + assertVersionSupports(LV.featureExtendedInterfaces, Some("ExerciseInterface.guard")) b.setGuard(g) } builder.setExerciseInterface(b) @@ -669,16 +667,16 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { b.setStruct(struct) b.setUpdate(update) builder.setStructUpd(b) - case EApps(fun, args) if languageVersion < LV.Features.flatArchive => + case EApps(fun, args) if !(versionSupports(LV.featureFlatArchive)) => builder.setApp(PLF.Expr.App.newBuilder().setFun(fun).accumulateLeft(args)(_ addArgs _)) - case EApp(fun, arg) if languageVersion >= LV.Features.flatArchive => + case EApp(fun, arg) if versionSupports(LV.featureFlatArchive) => builder.setApp(PLF.Expr.App.newBuilder().setFun(fun).addArgs(arg)) - case ETyApps(expr: Expr, typs0) if languageVersion < LV.Features.flatArchive => + case ETyApps(expr: Expr, typs0) if !(versionSupports(LV.featureFlatArchive)) => expr match { case EBuiltinFun(builtin) if indirectBuiltinFunctionMap.contains( builtin - ) && languageVersion < LV.Features.flatArchive => + ) && !(versionSupports(LV.featureFlatArchive)) => val typs = typs0.toSeq.toList builder.setBuiltin(indirectBuiltinFunctionMap(builtin)(typs).proto) case _ => @@ -686,23 +684,23 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { PLF.Expr.TyApp.newBuilder().setExpr(expr).accumulateLeft(typs0)(_ addTypes _) ) } - case ETyApp(expr, typ) if languageVersion >= LV.Features.flatArchive => + case ETyApp(expr, typ) if versionSupports(LV.featureFlatArchive) => builder.setTyApp( PLF.Expr.TyApp.newBuilder().setExpr(expr).addTypes(typ) ) - case EAbss(binders, body) if languageVersion < LV.Features.flatArchive => + case EAbss(binders, body) if !(versionSupports(LV.featureFlatArchive)) => builder.setAbs( PLF.Expr.Abs.newBuilder().accumulateLeft(binders)(_ addParam _).setBody(body) ) - case EAbs(binder, body) if languageVersion >= LV.Features.flatArchive => + case EAbs(binder, body) if versionSupports(LV.featureFlatArchive) => builder.setAbs( PLF.Expr.Abs.newBuilder().addParam(binder).setBody(body) ) - case ETyAbss(binders, body) if languageVersion < LV.Features.flatArchive => + case ETyAbss(binders, body) if !(versionSupports(LV.featureFlatArchive)) => builder.setTyAbs( PLF.Expr.TyAbs.newBuilder().accumulateLeft(binders)(_ addParam _).setBody(body) ) - case ETyAbs(binder, body) if languageVersion >= LV.Features.flatArchive => + case ETyAbs(binder, body) if versionSupports(LV.featureFlatArchive) => builder.setTyAbs( PLF.Expr.TyAbs.newBuilder().addParam(binder).setBody(body) ) @@ -825,7 +823,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { .setExpr(value) ) case EChoiceController(ty, choiceName, contract, choiceArg) => - assertSince(LV.Features.choiceFuncs, "Expr.ChoiceController") + assertVersionSupports(LV.featureChoiceFuncs, Some("Expr.ChoiceController")) val b = PLF.Expr.ChoiceController .newBuilder() .setTemplate(ty) @@ -834,7 +832,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { setInternedString(choiceName, b.setChoiceInternedStr) builder.setChoiceController(b) case EChoiceObserver(ty, choiceName, contract, choiceArg) => - assertSince(LV.Features.choiceFuncs, "Expr.ChoiceObserver") + assertVersionSupports(LV.featureChoiceFuncs, Some("Expr.ChoiceObserver")) val b = PLF.Expr.ChoiceObserver .newBuilder() .setTemplate(ty) @@ -843,7 +841,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { setInternedString(choiceName, b.setChoiceInternedStr) builder.setChoiceObserver(b) case EExperimental(name, ty) => - assertSince(LV.Features.unstable, "Expr.experimental") + assertVersionSupports(LV.featureUnstable) builder.setExperimental(PLF.Expr.Experimental.newBuilder().setName(name).setType(ty)) case ECallInterface(ty, methodName, expr) => @@ -970,7 +968,7 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { } choice.choiceAuthorizers match { case Some(value) => - assertSince(LV.Features.choiceAuthority, "TemplateChoice.authority") + assertVersionSupports(LV.featureChoiceAuthority, Some("TemplateChoice.authority")) b.setAuthorizers(value) case None => } @@ -1063,9 +1061,21 @@ private[daml] class EncodeV2(minorLanguageVersion: LV.Minor) { } - private def assertSince(minVersion: LV, description: String): Unit = - if (languageVersion < minVersion) - throw EncodeError(s"$description is not supported by Daml-LF $languageVersion") + private[this] def notSupportedError(description: String): EncodeError = + EncodeError(s"$description is not supported by Daml-LF $languageVersion") + + private def versionSupports(ft: LV.Feature): Boolean = + ft.versionReq.contains(languageVersion) + + def assertVersionSupports(ft: LV.Feature, caseDescription: Option[String] = None): Unit = { + if (!versionSupports(ft)) { + val optDescr = caseDescription match { + case Some(str) => s" (case ${str})" + case None => "" + } + throw notSupportedError(ft.name ++ optDescr) + } + } } diff --git a/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/DamlLfEncoderTest.scala b/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/DamlLfEncoderTest.scala index 945e20d806fe..cc629c98f5ae 100644 --- a/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/DamlLfEncoderTest.scala +++ b/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/DamlLfEncoderTest.scala @@ -164,7 +164,7 @@ class DamlLfEncoderTest .collect { case Ast.DValue(_, Ast.EBuiltinFun(builtin)) => builtin } .toSet val builtinsInVersion = DecodeV2.builtinFunctionInfos.collect { - case DecodeV2.BuiltinFunctionInfo(_, builtin, minVersion, maxVersion, _) + case DecodeV2.BuiltinFunctionInfo(_, builtin, minVersion, maxVersion, _, _) if minVersion <= version && maxVersion.forall(version < _) => builtin }.toSet diff --git a/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/EncodeSpec.scala b/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/EncodeSpec.scala index 5523d1e05b64..37920bca65de 100644 --- a/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/EncodeSpec.scala +++ b/sdk/daml-lf/encoder/src/test/scala/com/digitalasset/daml/lf/testing/archive/EncodeSpec.scala @@ -100,7 +100,7 @@ abstract class EncodeSpec(languageVersion: LanguageVersion) method asParty = Mod:Person {person} this; method getName = Mod:Person {name} this; }; -${onlyIf(languageVersion >= LanguageVersion.Features.contractKeys)(""" +${onlyIf(LanguageVersion.featureContractKeys.versionReq.contains(languageVersion))(""" key @Party (Mod:Person {person} this) (\ (p: Party) -> Cons @Party [p] (Nil @Party)); """)} }; @@ -173,7 +173,7 @@ ${onlyIf(languageVersion >= LanguageVersion.Features.contractKeys)(""" val anExercise: (ContractId Mod:Person) -> Update Unit = \(cId: ContractId Mod:Person) -> exercise @Mod:Person Sleep (Mod:identity @(ContractId Mod:Person) cId) (); -${onlyIf(languageVersion >= LanguageVersion.Features.contractKeys)(s""" +${onlyIf(LanguageVersion.featureContractKeys.versionReq.contains(languageVersion))(s""" val aFecthByKey: Party -> Update ($tuple2TyCon (ContractId Mod:Person) Mod:Person) = \\(party: Party) -> fetch_by_key @Mod:Person party; val aLookUpByKey: Party -> Update (Option (ContractId Mod:Person)) =\\(party: Party) -> @@ -187,7 +187,7 @@ ${onlyIf(languageVersion >= LanguageVersion.Features.contractKeys)(s""" uembed_expr @a x; val isZero: Int64 -> Bool = EQUAL @Int64 0; -${onlyIf(languageVersion >= LanguageVersion.Features.contractKeys)(""" +${onlyIf(LanguageVersion.featureContractKeys.versionReq.contains(languageVersion))(""" val isOne: BigNumeric -> Bool = EQUAL @BigNumeric (NUMERIC_TO_BIGNUMERIC @10 1.0000000000); val defaultRounding: RoundingMode = ROUNDING_UP; """)} @@ -216,7 +216,7 @@ ${onlyIf(languageVersion >= LanguageVersion.Features.contractKeys)(""" val concrete_from_interface: Mod:Human -> Option Mod:Person = \ (h: Mod:Human) -> from_interface @Mod:Human @Mod:Person h; -${onlyIf(languageVersion < LanguageVersion.Features.unsafeFromInterfaceRemoved)(""" +${onlyIf(LanguageVersion.featureUnsafeFromInterface.versionReq.contains(languageVersion))(""" val concrete_unsafe_from_interface: ContractId Mod:Human -> Mod:Human -> Mod:Person = \ (cid: ContractId Mod:Human) (h: Mod:Human) -> unsafe_from_interface @Mod:Human @Mod:Person cid h; """)} @@ -239,7 +239,7 @@ ${onlyIf(languageVersion < LanguageVersion.Features.unsafeFromInterfaceRemoved)( val concrete_observer_interface: Mod:Planet -> List Party = \ (p: Mod:Planet) -> observer_interface @Mod:Planet p; -${onlyIf(languageVersion >= LanguageVersion.Features.choiceFuncs)(""" +${onlyIf(LanguageVersion.featureChoiceFuncs.versionReq.contains(languageVersion))(""" val concrete_template_choice_controller: Mod:Person -> Int64 -> List Party = \ (p : Mod:Person) (i : Int64) -> choice_controller @Mod:Person Nap p i; val concrete_template_choice_observer: Mod:Person -> Int64 -> List Party = diff --git a/sdk/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Error.scala b/sdk/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Error.scala index 788e04b15247..4a9fa9e281f3 100644 --- a/sdk/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Error.scala +++ b/sdk/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Error.scala @@ -63,7 +63,7 @@ object Error { ) extends Error { def message: String = s"Disallowed language version in package $packageId: " + - s"Expected version between ${allowedLanguageVersions.min.pretty} and ${allowedLanguageVersions.max.pretty} but got ${languageVersion.pretty}" + s"Expected version range is ${allowedLanguageVersions.pretty} but got ${languageVersion.pretty}" } final case class DarSelfConsistency( diff --git a/sdk/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/UpgradesMatrix.scala b/sdk/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/UpgradesMatrix.scala index dea059cf6b4b..f0e971ff56a2 100644 --- a/sdk/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/UpgradesMatrix.scala +++ b/sdk/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/UpgradesMatrix.scala @@ -189,7 +189,7 @@ class UpgradesMatrixCases( ) def ifKeys[A](ifTrue: => A, ifFalse: => A): A = - if (langVersion >= LanguageVersion.Features.contractKeys) ifTrue else ifFalse + if (LanguageVersion.featureContractKeys.versionReq.contains(langVersion)) ifTrue else ifFalse def whenKeysOtherwiseNone[A](a: => A): Option[A] = ifKeys(Some(a), None) def whenKeysOtherwiseEmpty[A](a: => A)(implicit m: Monoid[A]) = ifKeys(a, m.empty) diff --git a/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/InterpreterTest.scala b/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/InterpreterTest.scala index 7ba91de1bb1a..30ac4ff60299 100644 --- a/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/InterpreterTest.scala +++ b/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/InterpreterTest.scala @@ -15,7 +15,7 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.prop.TableDrivenPropertyChecks import org.scalatest.wordspec.AnyWordSpec import com.daml.logging.ContextualizedLogger -import com.digitalasset.daml.lf.language.Ast +import com.digitalasset.daml.lf.language.{Ast, LanguageVersion} import scala.language.implicitConversions @@ -25,8 +25,9 @@ class InterpreterTest extends AnyWordSpec with Inside with Matchers with TableDr private implicit def id(s: String): Ref.Name = Name.assertFromString(s) - private val compilerConfig = Compiler.Config.Default - private val languageVersion = compilerConfig.allowedLanguageVersions.max + private val compilerConfig = + Compiler.Config.Default // should contain languageVersion defined below + private val languageVersion = LanguageVersion.latestStableLfVersion private def runExpr(e: Expr): SValue = { val machine = Speedy.Machine.fromPureExpr(PureCompiledPackages.Empty(compilerConfig), e) diff --git a/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/SBuiltinInterfaceTest.scala b/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/SBuiltinInterfaceTest.scala index a72a62f5b446..7842581dd001 100644 --- a/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/SBuiltinInterfaceTest.scala +++ b/sdk/daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/SBuiltinInterfaceTest.scala @@ -44,7 +44,8 @@ class SBuiltinInterfaceUpgradeImplementationTest extends AnyFreeSpec with Matche import EvalHelpers._ // TODO: revert to the default version and compiler config once they support upgrades - val languageVersion = LanguageVersion.Features.packageUpgrades + // TODO https://github.com/digital-asset/daml/issues/22365 further adopt feature ranges + val languageVersion = LanguageVersion.featurePackageUpgrades.versionReq.min val compilerConfig = Compiler.Config.Dev val alice = Ref.Party.assertFromString("Alice") @@ -219,7 +220,8 @@ class SBuiltinInterfaceUpgradeViewTest extends AnyFreeSpec with Matchers with In import org.scalatest.Inspectors.forEvery // TODO: revert to the default version and compiler config once they support upgrades - val languageVersion = LanguageVersion.Features.packageUpgrades + // TODO https://github.com/digital-asset/daml/issues/22365 further adopt feature ranges + val languageVersion = LanguageVersion.featurePackageUpgrades.versionReq.min val compilerConfig = Compiler.Config.Dev val alice = Ref.Party.assertFromString("Alice") diff --git a/sdk/daml-lf/language/BUILD.bazel b/sdk/daml-lf/language/BUILD.bazel index c2258d6b8b90..ac3b9d9e0251 100644 --- a/sdk/daml-lf/language/BUILD.bazel +++ b/sdk/daml-lf/language/BUILD.bazel @@ -24,9 +24,13 @@ load( da_scala_library( name = "language", srcs = glob(["src/main/**/*.scala"]) + [ + ":generated_scala_features_src", ":generated_scala_versions_src", ], - generated_srcs = [":generated_scala_versions_src"], + generated_srcs = [ + ":generated_scala_versions_src", + ":generated_scala_features_src", + ], scala_deps = [ "@maven//:org_scalaz_scalaz_core", ], @@ -119,3 +123,19 @@ py_binary( visibility = _VERSION_GENERATING_PACKAGES, deps = [], ) + +py_binary( + name = "generate_scala_features", + srcs = ["generate_scala_features.py"], + visibility = _VERSION_GENERATING_PACKAGES, +) + +genrule( + name = "generated_scala_features_src", + srcs = [ + "//daml-lf/language:feature_data_json", + ], + outs = ["src/main/scala/com/digitalasset/daml/lf/language/LanguageFeatureGen.scala"], + cmd = "$(location //daml-lf/language:generate_scala_features) $(location //daml-lf/language:feature_data_json) $@", + tools = ["//daml-lf/language:generate_scala_features"], +) diff --git a/sdk/daml-lf/language/daml-lf.bzl b/sdk/daml-lf/language/daml-lf.bzl index 6edb9501807a..611ba97f81e4 100644 --- a/sdk/daml-lf/language/daml-lf.bzl +++ b/sdk/daml-lf/language/daml-lf.bzl @@ -144,6 +144,31 @@ def _init_data(): "cpp_flag": "DAML_EXPERIMENTAL", "version_req": dev_only, }, + # used only in scala + { + "name": "featurePackageUpgrades", + "name_pretty": "Package upgrades", + "cpp_flag": "DAML_PackageUpgrades", + "version_req": {"low": V2_1}, + }, + # not sure what these below are + # not sure what these below are + # not sure what these below are + { + "name": "featureChoiceAuthority", + "name_pretty": "ChoiceAuthority???", + "cpp_flag": "DAML_ChoiceAuthority", + "version_req": dev_only, + }, + { + "name": "featureUnsafeFromInterface", + "name_pretty": "UnsafeFromInterface???", + "cpp_flag": "DAML_UnsafeFromInterface", + "version_req": {"high": V2_1}, + }, + # not sure what these above are + # not sure what these above are + # not sure what these above are ] return struct( diff --git a/sdk/daml-lf/language/generate_scala_features.py b/sdk/daml-lf/language/generate_scala_features.py new file mode 100644 index 000000000000..4625e9f5d191 --- /dev/null +++ b/sdk/daml-lf/language/generate_scala_features.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import json +import sys + +# --- Helpers for raw version definitions --- +# These are needed to parse the version_req structs +# inside features.json + +def to_scala_var(v): + """Converts a version dict to a Scala variable definition name, e.g., v2_1.""" + return "v{}_{}".format(v["major"], v["minor"]) + +def to_scala_major(major_str): + """Converts a major version string to a Scala MajorVersion constructor.""" + if major_str == "1": + return "V1" + if major_str == "2": + return "V2" + raise ValueError("Unsupported Major Version: {}".format(major_str)) + +def to_scala_minor(v): + """Converts a version dict to a Scala MinorVersion constructor.""" + status = v["status"] + minor = v["minor"] + if status == "stable": + return f"Stable({int(minor)})" + elif status == "staging": + return f"Staging({int(minor)})" + elif status == "dev": + return "Dev" + raise ValueError(f"Unsupported Status: {status}") + +# --- Helpers for feature definitions + +def to_scala_version_req(req_dict): + """Converts a version_req dict to a Scala VersionRange constructor.""" + low = req_dict.get("low") + high = req_dict.get("high") + + if low and high: + return f"VersionRange.Inclusive({to_scala_var(low)}, {to_scala_var(high)})" + elif low: + return f"VersionRange.From({to_scala_var(low)})" + elif high: + return f"VersionRange.Until({to_scala_var(high)})" + else: + # Note: Empty is now a case class, so it needs () + return "VersionRange.Empty()" + +# --- Static File Parts --- + +STATIC_HEADER = """// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// +// THIS FILE IS-GENERATED FROM //daml-lf/language/daml-lf.bzl +// (via generate_features.py) +// DO NOT EDIT MANUALLY +// + +package com.digitalasset.daml.lf +package language + +import com.digitalasset.daml.lf.language.LanguageVersion.Feature + +trait LanguageFeaturesGenerated extends LanguageVersionGenerated { + + // TODO (FEATURE): Remove this hardcoded object once V1 features are also generated + object LegacyFeatures { + val default = v1_6 + val internedPackageId = v1_6 + val internedStrings = v1_7 + val internedDottedNames = v1_7 + val numeric = v1_7 + val anyType = v1_7 + val typeRep = v1_7 + val typeSynonyms = v1_8 + val packageMetadata = v1_8 + val genComparison = v1_11 + val genMap = v1_11 + val scenarioMustFailAtMsg = v1_11 + val contractIdTextConversions = v1_11 + val exerciseByKey = v1_11 + val internedTypes = v1_11 + val choiceObservers = v1_11 + val bigNumeric = v1_13 + val exceptions = v1_14 + val basicInterfaces = v1_15 + val choiceFuncs = v1_dev + val choiceAuthority = v1_dev + val natTypeErasure = v1_dev + val packageUpgrades = v1_17 + val sharedKeys = v1_17 + val templateTypeRepToText = v1_dev + val extendedInterfaces = v1_dev + val unstable = v1_dev + } +""" + +STATIC_FOOTER = """ +} +""" + +def main(input_json_path, output_scala_path): + with open(input_json_path, 'r') as f: + features_data = json.load(f) + + output = [STATIC_HEADER] + output.append("\n // --- Generated V2 Features --- \n") + + all_feature_vars = [] + for feature in features_data: + var_name = feature["name"] + pretty_name = feature["name_pretty"] + cpp_flag = feature["cpp_flag"] + version_req_dict = feature["version_req"] + + scala_req = to_scala_version_req(version_req_dict) + + # Use json.dumps to safely escape strings for Scala + scala_pretty_name = json.dumps(pretty_name) + scala_cpp_flag = json.dumps(cpp_flag) + + output.append(f" val {var_name}: Feature = Feature(") + output.append(f" name = {scala_pretty_name},") + output.append(f" versionReq = {scala_req},") + output.append(f" cppFlag = {scala_cpp_flag},") + output.append( " )") + output.append( "") # newline + + all_feature_vars.append(var_name) + + # 4. Generate the allFeatures list + output.append(f" val allFeatures: List[Feature] = List({', '.join(all_feature_vars)})") + + output.append(STATIC_FOOTER) + + with open(output_scala_path, 'w') as f: + f.write("\n".join(output)) + +if __name__ == "__main__": + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ", file=sys.stderr) + sys.exit(1) + main(sys.argv[1], sys.argv[2]) diff --git a/sdk/daml-lf/language/generate_scala_versions.py b/sdk/daml-lf/language/generate_scala_versions.py index c6dbc8e2df59..eca76fbd10be 100644 --- a/sdk/daml-lf/language/generate_scala_versions.py +++ b/sdk/daml-lf/language/generate_scala_versions.py @@ -84,78 +84,9 @@ def generate_scala_singleton(name, version_dict): # closing brace for the trait. STATIC_FOOTER = """ //ranges hardcoded (for now) - val allLfVersionsRange = VersionRange(v2_1, v2_dev) - val stableLfVersionsRange = VersionRange(v2_1, v2_2) - val earlyAccessLfVersionsRange = VersionRange(v2_1, v2_2) - - //features hardcoded (for now) - object Features { - val default = v2_1 - val packageUpgrades = v2_1 - - val flatArchive = v2_2 - val kindInterning = flatArchive - val exprInterning = flatArchive - - val explicitPkgImports = v2_2 - - val choiceFuncs = v2_dev - val choiceAuthority = v2_dev - - /** TYPE_REP_TYCON_NAME builtin */ - val templateTypeRepToText = v2_dev - - /** Guards in interfaces */ - val extendedInterfaces = v2_dev - - /** BigNumeric */ - val bigNumeric = v2_dev - - val contractKeys = v2_dev - - val complexAnyType = v2_dev - - val cryptoUtility = v2_dev - - /** UNSAFE_FROM_INTERFACE is removed starting from 2.2, included */ - val unsafeFromInterfaceRemoved = v2_2 - - /** Unstable, experimental features. This should stay in x.dev forever. - * Features implemented with this flag should be moved to a separate - * feature flag once the decision to add them permanently has been made. - */ - val unstable = v2_dev - } - - object LegacyFeatures { - val default = v1_6 - val internedPackageId = v1_6 - val internedStrings = v1_7 - val internedDottedNames = v1_7 - val numeric = v1_7 - val anyType = v1_7 - val typeRep = v1_7 - val typeSynonyms = v1_8 - val packageMetadata = v1_8 - val genComparison = v1_11 - val genMap = v1_11 - val scenarioMustFailAtMsg = v1_11 - val contractIdTextConversions = v1_11 - val exerciseByKey = v1_11 - val internedTypes = v1_11 - val choiceObservers = v1_11 - val bigNumeric = v1_13 - val exceptions = v1_14 - val basicInterfaces = v1_15 - val choiceFuncs = v1_dev - val choiceAuthority = v1_dev - val natTypeErasure = v1_dev - val packageUpgrades = v1_17 - val sharedKeys = v1_17 - val templateTypeRepToText = v1_dev - val extendedInterfaces = v1_dev - val unstable = v1_dev - } + val allLfVersionsRange: VersionRange.Inclusive[LanguageVersion] = VersionRange(v2_1, v2_dev) + val stableLfVersionsRange: VersionRange.Inclusive[LanguageVersion] = VersionRange(v2_1, v2_2) + val earlyAccessLfVersionsRange: VersionRange.Inclusive[LanguageVersion] = VersionRange(v2_1, v2_2) } """ diff --git a/sdk/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/LanguageVersion.scala b/sdk/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/LanguageVersion.scala index bb6eaf4aac7a..940632da0afe 100644 --- a/sdk/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/LanguageVersion.scala +++ b/sdk/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/LanguageVersion.scala @@ -18,7 +18,7 @@ final case class LanguageVersion private ( } } -object LanguageVersion extends LanguageVersionGenerated { +object LanguageVersion extends LanguageFeaturesGenerated { sealed abstract class Major(val major: Int) extends Product with Serializable @@ -82,6 +82,14 @@ object LanguageVersion extends LanguageVersionGenerated { def assertFromString(s: String): Minor = data.assertRight(fromString(s)) } + final case class Feature( + name: String, + versionReq: VersionRange[LanguageVersion], + cppFlag: String, + ) { + def enabledIn(lv: LanguageVersion): Boolean = versionReq.contains(lv) + } + def fromString(str: String): Either[String, LanguageVersion] = (allLegacyLfVersions ++ allLfVersions) .find(_.toString == str) @@ -91,28 +99,15 @@ object LanguageVersion extends LanguageVersionGenerated { // TODO: remove after feature rework def supportsPackageUpgrades(lv: LanguageVersion): Boolean = lv.major match { - case Major.V2 => lv >= Features.packageUpgrades + case Major.V2 => featurePackageUpgrades.enabledIn(lv) case Major.V1 => lv >= LegacyFeatures.packageUpgrades } // TODO: remove after feature rework (this reworks ranges too, so this can be replaced by an Until range) - def allUpToVersion(version: LanguageVersion): VersionRange[LanguageVersion] = { + def allUpToVersion(version: LanguageVersion): VersionRange.Inclusive[LanguageVersion] = { version.major match { case Major.V2 => VersionRange(v2_1, version) case _ => throw new IllegalArgumentException(s"${version.major.pretty} not supported") } } - - // @deprecated("Actually not sure if deprecated", since="3.5") - object LanguageVersionRangeOps { - implicit class LanguageVersionRange(val range: VersionRange[LanguageVersion]) { - def majorVersion: Major = { - require( - range.min.major == range.max.major, - s"version range ${range} spans over multiple version LF versions", - ) - range.max.major - } - } - } } diff --git a/sdk/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ModParser.scala b/sdk/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ModParser.scala index 05816238c5b2..3a110217a8ee 100644 --- a/sdk/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ModParser.scala +++ b/sdk/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ModParser.scala @@ -55,7 +55,7 @@ private[parser] class ModParser[P](parameters: ParserParameters[P]) { languageVersion = parameters.languageVersion, metadata = metadata, imports = - if (languageVersion >= LV.Features.explicitPkgImports) + if (LV.featurePackageImports.versionReq.contains(languageVersion)) DeclaredImports(pkgIds = mentionedPackageIdsMinusStablePackages) else GeneratedImports( diff --git a/sdk/daml-lf/stable-packages/src/main/scala/com/digitalasset/daml/lf/stablepackages/StablePackages.scala b/sdk/daml-lf/stable-packages/src/main/scala/com/digitalasset/daml/lf/stablepackages/StablePackages.scala index 718440e638c4..8f38c1eb14d5 100644 --- a/sdk/daml-lf/stable-packages/src/main/scala/com/digitalasset/daml/lf/stablepackages/StablePackages.scala +++ b/sdk/daml-lf/stable-packages/src/main/scala/com/digitalasset/daml/lf/stablepackages/StablePackages.scala @@ -21,7 +21,7 @@ private[daml] object StablePackages { def ids(allowedLanguageVersions: VersionRange[LanguageVersion]): Set[Ref.PackageId] = { StablePackages.stablePackages.allPackages.view - .filter(_.pkg.languageVersion <= allowedLanguageVersions.max) + .filter(p => allowedLanguageVersions.contains(p.pkg.languageVersion)) .map(_.packageId) .toSet } diff --git a/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/transaction/test/TransactionBuilder.scala b/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/transaction/test/TransactionBuilder.scala index c0283260f0a0..3cedbf4c3e63 100644 --- a/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/transaction/test/TransactionBuilder.scala +++ b/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/transaction/test/TransactionBuilder.scala @@ -7,14 +7,8 @@ package test import com.digitalasset.daml.lf.data._ import com.digitalasset.daml.lf.value.Value -import com.digitalasset.daml.lf.value.Value.{ - ContractId, - ThinContractInstance, - VersionedThinContractInstance, -} +import com.digitalasset.daml.lf.value.Value.ContractId -import scala.Ordering.Implicits.infixOrderingOps -import scala.annotation.tailrec import scala.collection.immutable.{HashMap, TreeSet} import scala.language.implicitConversions @@ -79,72 +73,9 @@ object TransactionBuilder { val EmptyCommitted: CommittedTransaction = CommittedTransaction(Empty) def assignVersion[Cid]( - v0: Value, - supportedVersions: VersionRange[SerializationVersion] = SerializationVersion.StableVersions, - ): Either[String, SerializationVersion] = { - @tailrec - def go( - currentVersion: SerializationVersion, - values0: FrontStack[Value], - ): Either[String, SerializationVersion] = { - import Value._ - if (currentVersion >= supportedVersions.max) { - Right(currentVersion) - } else { - values0.pop match { - case None => Right(currentVersion) - case Some((value, values)) => - value match { - // for things supported since version 1, we do not need to check - case ValueRecord(_, fs) => go(currentVersion, fs.map(v => v._2) ++: values) - case ValueVariant(_, _, arg) => go(currentVersion, arg +: values) - case ValueList(vs) => go(currentVersion, vs.toImmArray ++: values) - case ValueContractId(_) | ValueInt64(_) | ValueText(_) | ValueTimestamp(_) | - ValueParty(_) | ValueBool(_) | ValueDate(_) | ValueUnit | ValueNumeric(_) => - go(currentVersion, values) - case ValueOptional(x) => - go(currentVersion, x.fold(values)(_ +: values)) - case ValueTextMap(map) => - go(currentVersion, map.values ++: values) - case ValueEnum(_, _) => - go(currentVersion, values) - case ValueGenMap(entries) => - val newValues = entries.iterator.foldLeft(values) { case (acc, (key, value)) => - key +: value +: acc - } - go(currentVersion, newValues) - } - } - } - } - - go(supportedVersions.min, FrontStack(v0)) match { - case Right(inferredVersion) if supportedVersions.max < inferredVersion => - Left(s"inferred version $inferredVersion is not supported") - case res => - res - } - - } - @throws[IllegalArgumentException] - def assertAssignVersion( - v0: Value, - supportedVersions: VersionRange[SerializationVersion] = SerializationVersion.DevVersions, - ): SerializationVersion = - data.assertRight(assignVersion(v0, supportedVersions)) - - def asVersionedContract( - contract: ThinContractInstance, - supportedVersions: VersionRange[SerializationVersion] = SerializationVersion.DevVersions, - ): Either[String, VersionedThinContractInstance] = - assignVersion(contract.arg, supportedVersions) - .map(Versioned(_, contract)) - - def assertAsVersionedContract( - contract: ThinContractInstance, - supportedVersions: VersionRange[SerializationVersion] = SerializationVersion.DevVersions, - ): VersionedThinContractInstance = - data.assertRight(asVersionedContract(contract, supportedVersions)) + supportedVersions: VersionRange.Inclusive[SerializationVersion] = + SerializationVersion.StableVersions + ): SerializationVersion = supportedVersions.min object Implicits { diff --git a/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/value/test/ValueGenerators.scala b/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/value/test/ValueGenerators.scala index 735011a7c929..095c32e3c7ca 100644 --- a/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/value/test/ValueGenerators.scala +++ b/sdk/daml-lf/transaction-test-lib/src/main/scala/com/digitalasset/daml/lf/value/test/ValueGenerators.scala @@ -289,7 +289,7 @@ object ValueGenerators { def versionedValueGen: Gen[VersionedValue] = for { value <- valueGen() - minVersion = TransactionBuilder.assertAssignVersion(value) + minVersion = TransactionBuilder.assignVersion() version <- SerializationVersionGen(minVersion) } yield Versioned(version, value) diff --git a/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/SerializationVersion.scala b/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/SerializationVersion.scala index ecf7ab40dd3f..e22371d97aef 100644 --- a/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/SerializationVersion.scala +++ b/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/SerializationVersion.scala @@ -70,11 +70,15 @@ object SerializationVersion { val minVersion: SerializationVersion = All.min val maxVersion: SerializationVersion = All.max + // TODO https://github.com/digital-asset/daml/issues/22365 adopt ranges more thoroughly private[lf] val minContractKeys: SerializationVersion = assign( - LanguageVersion.Features.contractKeys + LanguageVersion.featureContractKeys.versionReq.min ) - private[lf] val minChoiceAuthorizers = assign(LanguageVersion.Features.choiceAuthority) + // TODO https://github.com/digital-asset/daml/issues/22365 adopt ranges more thoroughly + private[lf] val minChoiceAuthorizers = assign( + LanguageVersion.featureChoiceAuthority.versionReq.min + ) private[lf] def txVersion(tx: Transaction): SerializationVersion = { import scala.Ordering.Implicits._ @@ -89,10 +93,10 @@ object SerializationVersion { ): VersionedTransaction = VersionedTransaction(txVersion(tx), tx.nodes, tx.roots) - val StableVersions: VersionRange[SerializationVersion] = + val StableVersions: VersionRange.Inclusive[SerializationVersion] = LanguageVersion.stableLfVersionsRange.map(assign) - private[lf] val DevVersions: VersionRange[SerializationVersion] = + private[lf] val DevVersions: VersionRange.Inclusive[SerializationVersion] = LanguageVersion.allLfVersionsRange.map(assign) } diff --git a/sdk/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala b/sdk/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala index 2c5088f23fb8..05faf4b7f1f5 100644 --- a/sdk/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala +++ b/sdk/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala @@ -1366,7 +1366,7 @@ private[validation] object Typing { } private def checkAnyType(typ: Type): Unit = { - if (languageVersion >= LanguageVersion.Features.complexAnyType) + if (LanguageVersion.featureComplexAnyType.versionReq.contains(languageVersion)) checkAnyType_(typ) else typ match { diff --git a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ledgerinteraction/IdeLedgerClient.scala b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ledgerinteraction/IdeLedgerClient.scala index fac55fac470c..17a90d7a6663 100644 --- a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ledgerinteraction/IdeLedgerClient.scala +++ b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ledgerinteraction/IdeLedgerClient.scala @@ -108,7 +108,7 @@ class IdeLedgerClient( .lookupPackage(packageId) .fold( _ => false, - pkgSig => pkgSig.languageVersion >= LanguageVersion.Features.packageUpgrades, + pkgSig => LanguageVersion.featurePackageUpgrades.versionReq.contains(pkgSig.languageVersion), ) private var _ledger: IdeLedger = IdeLedger.initialLedger(Time.Timestamp.Epoch)