Repository: apple/pkl
File: pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java (Java)

Code before:
    public ConstrainedTypeNode(
        SourceSection sourceSection, TypeNode childNode, TypeConstraintNode[] constraintNodes) {
      super(sourceSection);
      this.childNode = childNode;
      this.constraintNodes = constraintNodes;
    }

    @Override
    public FrameSlotKind getFrameSlotKind() {
      return childNode.getFrameSlotKind();
    }

    @Override
    public TypeNode initWriteSlotNode(int slot) {
      childNode.initWriteSlotNode(slot);
      return this;
    }

    @ExplodeLoop
    public Object execute(VirtualFrame frame, Object value) {
      if (customThisSlot == -1) {
        CompilerDirectives.transferToInterpreterAndInvalidate();
        // deferred until execution time s.t. nodes of inlined type aliases get the right frame slot
        customThisSlot =
            frame.getFrameDescriptor().findOrAddAuxiliarySlot(CustomThisScope.FRAME_SLOT_ID);
      }

      var ret = childNode.execute(frame, value);

      frame.setAuxiliarySlot(customThisSlot, value);
      for (var node : constraintNodes) {
        node.execute(frame);
      }
      return ret;
    }

    @Override
    public Object executeAndSet(VirtualFrame frame, Object value) {
      var ret = execute(frame, value);
      childNode.executeAndSet(frame, ret);
      return ret;
    }

    @Override
    public @Nullable Object createDefaultValue(
        VmLanguage language, SourceSection headerSection, String qualifiedName) {

      return childNode.createDefaultValue(language, headerSection, qualifiedName);
    }

    public SourceSection getBaseTypeSection() {

Review comment (bug):
As part of this fix, I also realized that this logic is incorrect. We don't know that this node will always be called from the same frame descriptor, because this same node might be called from different root nodes.

Added a test (constraints14.pkl) that covers this edge case.

Code after:
    // *Maybe* this works out for TypeAliasTypeNode because an object instantiated
    // within a type constraint doesn't escape the constraint expression.
    // If mutating receiver and owner can't be avoided, it would be safer
    // to have VmObjectLike store them directly instead of storing enclosingFrame.
    private static void setReceiver(Frame frame, Object receiver) {
      frame.getArguments()[0] = receiver;
    }

    private static void setOwner(Frame frame, VmObjectLike owner) {
      frame.getArguments()[1] = owner;
    }
  }

  public static final class ConstrainedTypeNode extends TypeNode {

    private final VmLanguage language;
    @Child private TypeNode childNode;
    @Children private final TypeConstraintNode[] constraintNodes;

    public ConstrainedTypeNode(
        SourceSection sourceSection,
        VmLanguage language,
        TypeNode childNode,
        TypeConstraintNode[] constraintNodes) {
      super(sourceSection);
      this.language = language;
      this.childNode = childNode;
      this.constraintNodes = constraintNodes;
    }

    @Override
    public FrameSlotKind getFrameSlotKind() {
      return childNode.getFrameSlotKind();
    }

    @Override
    public TypeNode initWriteSlotNode(int slot) {
      childNode.initWriteSlotNode(slot);
      return this;
    }

    @ExplodeLoop
    protected Object executeLazily(VirtualFrame frame, Object value) {
      var customThisSlot =
          frame.getFrameDescriptor().findOrAddAuxiliarySlot(CustomThisScope.FRAME_SLOT_ID);

      var ret = childNode.executeLazily(frame, value);

      var localContext = language.localContext.get();
      var prevShouldTypeCheck = localContext.shouldEagerTypecheck();
      localContext.shouldEagerTypecheck(true);