Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make exercise by interface contract ID safe #14134

Merged
merged 32 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e0f495d
patch in development paths temporarily
S11001001 Jun 8, 2022
cc23f80
interface stubs
S11001001 Jun 8, 2022
0832c14
template, what works and doesn't work
S11001001 Jun 8, 2022
09e2214
new test cases
S11001001 Jun 8, 2022
6c41747
variance guesses
S11001001 Jun 8, 2022
0ae7207
typing interface choices and contract IDs differently
S11001001 Jun 9, 2022
384294c
check uninhabitance
S11001001 Jun 9, 2022
d7f092a
better marker, the template contract ID discrimination problem
S11001001 Jun 9, 2022
63344da
toInterface; negation doesn't quite work
S11001001 Jun 9, 2022
a0489f5
further tests
S11001001 Jun 9, 2022
cb96512
try intersection; infers never, which is not super helpful
S11001001 Jun 9, 2022
2307620
turn never into unknown; test cases work, at the cost of a complex to…
S11001001 Jun 9, 2022
71778d4
turn never into unknown; test cases work, at the cost of a complex to…
S11001001 Jun 9, 2022
11bcfe0
notes on some of the quirks
S11001001 Jun 9, 2022
ad423a0
remove unused intersection
S11001001 Jun 9, 2022
7908358
Merge commit 'ba6f377f29e539ce562d25965e3598d8f7272ddc' into 14132-ex…
S11001001 Aug 23, 2022
e8add20
add another argument to toInterface and unsafeFromInterface to suppor…
S11001001 Aug 23, 2022
db2267a
Merge commit '7f4aefd09a207d72801011dbf68050f2f7926cdb' into 14132-ex…
S11001001 Aug 30, 2022
9e1676e
test that intersection doesn't introduce never to retroactives
S11001001 Aug 30, 2022
20e9d48
declare Interface, ToInterface, FromInterface in @daml/types library
S11001001 Aug 30, 2022
3df522a
generate ToInterface and FromTemplate companion declarations
S11001001 Aug 30, 2022
6a1a2b6
add toInterface and unsafeFromInterface to template companion impleme…
S11001001 Aug 30, 2022
420a1d3
remove now-unneeded note about implementation union
S11001001 Aug 30, 2022
bcbda5b
declare marker types for every interface
S11001001 Aug 30, 2022
4a94af9
remove unused variables
S11001001 Aug 30, 2022
f43cd6b
remove prototype
S11001001 Aug 30, 2022
4b039f6
remove type parameter from interface companions' types
S11001001 Aug 30, 2022
bad01fd
add the union for forward implements
S11001001 Aug 30, 2022
65168ed
make test.ts compile and pass again
S11001001 Aug 30, 2022
26e5860
update temp test paths
S11001001 Aug 30, 2022
5f533c4
undo temp test paths
S11001001 Aug 30, 2022
97c1557
add changelog
S11001001 Aug 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions language-support/ts/codegen/src/TsCodeGenMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -442,13 +442,17 @@ renderTemplateDef TemplateDef {..} =
((\(tsRef, _, inChcs) -> (tsRef, inChcs)) <$> tplImplements')
tplChoices'
, [ "export declare const " <> tplName <> ":"
, " damlTypes.Template<" <> tplName <> ", " <> keyTy <> ", '" <> templateId <> "'> & " <> tplName <> "Interface;"
, " damlTypes.Template<" <> tplName <> ", " <> keyTy <> ", '" <> templateId <> "'> &"
, " damlTypes.ToInterface<" <> tplName <> ", " <> implsUnion <> "> &"
, " " <> tplName <> "Interface;"
]
]
in (jsSource, tsDecl)
where (keyTy, keyDec) = case tplKeyDecoder of
Nothing -> ("undefined", DecoderConstant ConstantUndefined)
Just d -> (tplName <> ".Key", DecoderLazy d)
implsUnion = if null tplImplements' then "never"
else T.intercalate " | " [ impl | (TsTypeConRef impl, _, _) <- tplImplements' ]
templateId =
unPackageId tplPkgId <> ":" <>
T.intercalate "." (unModuleName tplModule) <> ":" <>
Expand Down Expand Up @@ -483,8 +487,16 @@ renderInterfaceDef InterfaceDef{ifName, ifChoices, ifModule, ifPkgId} = (jsSourc
, [ "};" ]
]
tsDecl = T.unlines $ concat
[ifaceDefIface ifName Nothing ifChoices
, ["export declare const " <> ifName <> ": damlTypes.Template<object, undefined, '" <> ifaceId <> "'> & " <> ifName <> "Interface<object>;"]
[ [ "export declare type " <> ifName <> " = damlTypes.Interface<"
<> renderDecoderConstant (ConstantString ifaceId) <> ">;" ]
, ifaceDefIface ifName Nothing ifChoices
, [ "export declare const " <> ifName <> ":"
, " damlTypes.Template<" <> ifName <> ", undefined, '" <> ifaceId <> "'> &"
-- TODO #14082 pass an intersection of type refs to retroImplements when
-- non-empty; 'unknown' is correct if empty
, " damlTypes.FromTemplate<" <> ifName <> ", unknown> &"
, " " <> ifName <> "Interface;"
]
]
ifaceId =
unPackageId ifPkgId <> ":" <>
Expand All @@ -503,17 +515,16 @@ ifaceDefTempl name mbKeyTy impls choices =
, [ "}" ]
]
where
mbSubst = Just (Set.fromList . map fst $ impls, implTy)
mbSubst = Nothing
keyTy = fromMaybe "undefined" mbKeyTy
extension
| null impls = ""
| otherwise = "extends " <> implTy'
implTy = T.intercalate " & " implRefs
implTy' = T.intercalate " , " implRefs
implRefs = [if Set.null omit then baseInherit
else "Omit<" <> baseInherit <> ", " <> literalOmit <> ">"
| ((TsTypeConRef impl, _), omit) <- impls `zip` omitFromExtends
, let baseInherit = impl <> "Interface<" <> name <> ">"
, let baseInherit = impl <> "Interface"
literalOmit = T.intercalate " | "
. map (renderDecoderConstant . ConstantString)
. Set.toList $ omit]
Expand Down Expand Up @@ -544,16 +555,16 @@ uniques =
ifaceDefIface :: T.Text -> Maybe T.Text -> [ChoiceDef] -> [T.Text]
ifaceDefIface name mbKeyTy choices =
concat
[ ["export declare interface " <> name <> "Interface " <> "<T extends object>{"]
[ ["export declare interface " <> name <> "Interface " <> "{"]
, [ " " <> chcName' <> ": damlTypes.Choice<" <>
"T, " <>
name <> ", " <>
tsTypeRef (genType chcArgTy mbSubst) <> ", " <>
tsTypeRef (genType chcRetTy mbSubst) <> ", " <>
keyTy <> ">;" | ChoiceDef{..} <- choices ]
, [ "}" ]
]
where
mbSubst = Just (Set.singleton (TsTypeConRef name), name <> "Interface<T>")
mbSubst = Nothing
keyTy = fromMaybe "undefined" mbKeyTy

data ChoiceDef = ChoiceDef
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ describe("interface definition", () => {
// Something is inherited
test("unambiguous inherited is inherited", () => {
const c: Choice<
buildAndLint.Main.Asset,
buildAndLint.Lib.Mod.Other,
buildAndLint.Lib.Mod.Something,
{},
undefined
Expand Down Expand Up @@ -643,6 +643,8 @@ describe("interface definition", () => {
});

test("interfaces", async () => {
const Asset = buildAndLint.Main.Asset;
const Token = buildAndLint.Main.Token;
const aliceLedger = new Ledger({
token: ALICE_TOKEN,
httpBaseUrl: httpBaseUrl(),
Expand All @@ -662,8 +664,8 @@ test("interfaces", async () => {
);
expect(ifaceContract.payload).toEqual(assetPayload);
const [, events1] = await aliceLedger.exercise(
buildAndLint.Main.Asset.Transfer,
ifaceContract.contractId,
Asset.Transfer,
Asset.toInterface(Token, ifaceContract.contractId),
{ newOwner: BOB_PARTY },
);
expect(events1).toMatchObject([
Expand All @@ -687,7 +689,7 @@ test("interfaces", async () => {
);
const [, events2] = await bobLedger.exercise(
buildAndLint.Main.Token.Transfer,
ifaceContract2.contractId,
Asset.toInterface(Token, ifaceContract2.contractId),
{ newOwner: ALICE_PARTY },
);
expect(events2).toMatchObject([
Expand Down
83 changes: 79 additions & 4 deletions language-support/ts/daml-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,65 @@ export interface Template<
Archive: Choice<T, {}, {}, K>;
}

/**
* A mixin for [[Template]] that provides the `toInterface` and
* `unsafeFromInterface` contract ID conversion functions.
*
* Even templates that directly implement no interfaces implement this, because
* this also permits conversion with interfaces that supply retroactive
* implementations to this template.
*
* @typeparam T The template type.
* @typeparam IfU The union of implemented interfaces, or `never` for templates
* that directly implement no interface.
*/
export interface ToInterface<T extends object, IfU> {
// overload for direct interface implementations
toInterface<If extends IfU>(
ic: FromTemplate<If, unknown>,
cid: ContractId<T>,
): ContractId<If>;
// overload for retroactive interface implementations
toInterface<If>(ic: FromTemplate<If, T>, cid: ContractId<T>): ContractId<If>;

// overload for direct interface implementations
unsafeFromInterface(
ic: FromTemplate<IfU, unknown>,
cid: ContractId<IfU>,
): ContractId<T>;
// overload for retroactive interface implementations
unsafeFromInterface<If>(
ic: FromTemplate<If, T>,
cid: ContractId<If>,
): ContractId<T>;
}

const InterfaceBrand: unique symbol = Symbol();

/**
* An interface type, for use with contract IDs.
*
* @typeparam IfId The interface ID as a constant string.
*/
export type Interface<IfId> = { readonly [InterfaceBrand]: IfId };

const FromTemplateBrand: unique symbol = Symbol();

/**
* Interface for objects representing Daml interfaces. This supplies the basis
* for the methods of [[ToInterface]].
*
* Even interfaces that retroactively implement for no templates implement this,
* because forward implementations still require this marker to work.
*
* @typeparam If The interface type.
* @typeparam TX The intersection of template types this interface retroactively
* implements, or `unknown` if there are none.
*/
export interface FromTemplate<If, TX> {
readonly [FromTemplateBrand]: [If, TX];
}

/**
* Interface for objects representing Daml choices.
*
Expand Down Expand Up @@ -87,13 +146,25 @@ export interface Choice<T extends object, C, R, K = unknown> {
choiceName: string;
}

function toInterfaceMixin<T extends object, IfU>(): ToInterface<T, IfU> {
return {
toInterface<If>(_: FromTemplate<If, unknown>, cid: ContractId<T>) {
return cid as ContractId<never> as ContractId<If>;
},

unsafeFromInterface<If>(_: FromTemplate<If, unknown>, cid: ContractId<If>) {
return cid as ContractId<never> as ContractId<T>;
},
};
}

/**
* @internal
*/
export function assembleTemplate<T extends object>(
export function assembleTemplate<T extends object, IfU>(
template: Template<T>,
...interfaces: Template<object>[]
): Template<T> {
...interfaces: FromTemplate<IfU, unknown>[]
): Template<T> & ToInterface<T, IfU> {
const combined = {};
const overloaded: string[] = [];
for (const iface of interfaces) {
Expand All @@ -102,7 +173,11 @@ export function assembleTemplate<T extends object>(
return undefined;
});
}
return Object.assign(_.omit(combined, overloaded), template);
return Object.assign(
_.omit(combined, overloaded),
toInterfaceMixin<T, IfU>(),
template,
);
}

/**
Expand Down