From 0fccffacb2d3928a39ef08ce1259273fd2cf7a1a Mon Sep 17 00:00:00 2001 From: Joe Eli McIlvain Date: Fri, 22 Apr 2022 14:32:37 -0700 Subject: [PATCH] Allow non-sendable params to an elevated constructor when the type has no fields. The rule preventing non-sendable params passed to an elevated-cap constructor is there to prevent the possibility of the params becoming entangled in the new object's fields. However, if the new object has no fields, it is easily shown that there is no risk of such entanglement. Hence, we can allow it. When is it useful to have an `iso` object with no fields? This pattern is used to create a non-replicable ticket, such as the `StdIn.Ticket` used in the `StdIn` library. The constructor for `StdIn.Ticket` takes the `Env.Root.TicketIssuer'ref` as a parameter (which is not sendable), but as mentioned in this ticket, it is safe to do so because it's not possible to become entangled with it. --- .../type_check.validation.savi.spec.md | 42 ++++++++++++------- src/savi/compiler/type_check.cr | 12 +++--- src/savi/program.cr | 4 ++ 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/spec/compiler/type_check.validation.savi.spec.md b/spec/compiler/type_check.validation.savi.spec.md index f1c883e1..5cae4e16 100644 --- a/spec/compiler/type_check.validation.savi.spec.md +++ b/spec/compiler/type_check.validation.savi.spec.md @@ -2,9 +2,32 @@ pass: type_check --- +It complains if some params of an asynchronous function are not sendable: + +```savi +:actor BadActor + :be bad_behavior(a String'ref, b String'val, c String'box) +``` +```error +An asynchronous function must only have sendable parameters: + :be bad_behavior(a String'ref, b String'val, c String'box) + ^~ + +- this parameter type (String'ref) is not sendable: + :be bad_behavior(a String'ref, b String'val, c String'box) + ^~~~~~~~~~~~ + +- this parameter type (String'box) is not sendable: + :be bad_behavior(a String'ref, b String'val, c String'box) + ^~~~~~~~~~~~ +``` + +--- + It complains if some params of an elevated constructor are not sendable: ```savi + :let field String'ref: String.new :new iso iso_constructor(a String'ref, b String'val, c String'box) :new val val_constructor(a String'ref, b String'val, c String'box) :new box box_constructor(a String'ref, b String'val, c String'box) @@ -38,24 +61,11 @@ A constructor with elevated capability must only have sendable parameters: --- -It complains if some params of an asynchronous function are not sendable: +It allows non-sendable params to an elevated constructor if there are no fields: ```savi -:actor BadActor - :be bad_behavior(a String'ref, b String'val, c String'box) -``` -```error -An asynchronous function must only have sendable parameters: - :be bad_behavior(a String'ref, b String'val, c String'box) - ^~ - -- this parameter type (String'ref) is not sendable: - :be bad_behavior(a String'ref, b String'val, c String'box) - ^~~~~~~~~~~~ - -- this parameter type (String'box) is not sendable: - :be bad_behavior(a String'ref, b String'val, c String'box) - ^~~~~~~~~~~~ + // (no fields) + :new iso iso_constructor(a String'ref, b String'val, c String'box) ``` --- diff --git a/src/savi/compiler/type_check.cr b/src/savi/compiler/type_check.cr index 32206235..13ee01a9 100644 --- a/src/savi/compiler/type_check.cr +++ b/src/savi/compiler/type_check.cr @@ -769,11 +769,13 @@ class Savi::Compiler::TypeCheck require_sendable = if @func.has_tag?(:async) "An asynchronous function" - elsif @func.has_tag?(:constructor) \ - && !MetaType.cap(Cap::REF).subtype_of?(ctx, - resolve(ctx, @pre_infer[ret]).not_nil!.cap_only - ) - "A constructor with elevated capability" + elsif @func.has_tag?(:constructor) + ret_mt = resolve(ctx, @pre_infer[ret]).not_nil! + ret_rt = ret_mt.single_rt?.not_nil! + if !MetaType.cap(Cap::REF).subtype_of?(ctx, ret_mt.cap_only) \ + && ret_rt.defn(ctx).has_any_fields? + "A constructor with elevated capability" + end end if require_sendable @func.params.try do |params| diff --git a/src/savi/program.cr b/src/savi/program.cr index de89ce62..4edcc20e 100644 --- a/src/savi/program.cr +++ b/src/savi/program.cr @@ -267,6 +267,10 @@ class Savi::Program io << "#<#{self.class} #{@ident.value}>" end + def has_any_fields? + @functions.any?(&.has_tag?(:field)) + end + def find_func?(func_name) @functions .find { |f| f.ident.value == func_name && !f.has_tag?(:hygienic) }