@Specialization(guards = {"!copyAllAttributes || target != source", "containsMetadata(source)"})
  protected RAbstractVector copySameLength(
      RAbstractVector target,
      RAbstractVector source, //
      @Cached("create()") CopyOfRegAttributesNode copyOfReg, //
      @Cached("createDim()") RemoveFixedAttributeNode removeDim, //
      @Cached("createDimNames()") RemoveFixedAttributeNode removeDimNames, //
      @Cached("create()") InitAttributesNode initAttributes, //
      @Cached("createNames()") SetFixedAttributeNode putNames, //
      @Cached("createDim()") SetFixedAttributeNode putDim, //
      @Cached("createDimNames()") SetFixedAttributeNode putDimNames, //
      @Cached("createBinaryProfile()") ConditionProfile noDimensions, //
      @Cached("createBinaryProfile()") ConditionProfile hasNamesSource, //
      @Cached("createBinaryProfile()") ConditionProfile hasDimNames,
      @Cached("create()") GetDimAttributeNode getDimsNode) {
    RVector<?> result = target.materialize();

    if (copyAllAttributes) {
      copyOfReg.execute(source, result);
    }

    int[] newDimensions = getDimsNode.getDimensions(source);
    if (noDimensions.profile(newDimensions == null)) {
      DynamicObject attributes = result.getAttributes();
      if (attributes != null) {
        removeDim.execute(attributes);
        removeDimNames.execute(attributes);
      }

      RStringVector vecNames = getNamesNode.getNames(source);
      if (hasNamesSource.profile(vecNames != null)) {
        putNames.execute(initAttributes.execute(result), vecNames);
        return result;
      }
      return result;
    }

    putDim.execute(
        initAttributes.execute(result),
        RDataFactory.createIntVector(newDimensions, RDataFactory.COMPLETE_VECTOR));

    RList newDimNames = getDimNamesNode.getDimNames(source);
    if (hasDimNames.profile(newDimNames != null)) {
      putDimNames.execute(result.getAttributes(), newDimNames);
      newDimNames.elementNamePrefix = RRuntime.DIMNAMES_LIST_ELEMENT_NAME_PREFIX;
      return result;
    }
    return result;
  }
 protected boolean containsMetadata(RAbstractVector vector) {
   return vector instanceof RVector && hasDimNode.execute(vector)
       || (copyAllAttributes && vector.getAttributes() != null)
       || getNamesNode.getNames(vector) != null
       || getDimNamesNode.getDimNames(vector) != null;
 }
/**
 * Copies all attributes from source to target including 'names', 'dimNames' and 'dim' (unlike
 * {@link CopyOfRegAttributesNode}), additionally removes the 'dim' from the result if it is not
 * present in the source.
 *
 * <p>TODO: this logic is duplicated in RVector#copyRegAttributesFrom and UnaryMapNode, but behind
 * TruffleBoundary, does it have a reason for TruffleBoundary? Can we replace it with this node?
 */
public abstract class UnaryCopyAttributesNode extends RBaseNode {

  protected final boolean copyAllAttributes;

  @Child protected HasFixedAttributeNode hasDimNode = HasFixedAttributeNode.createDim();
  @Child protected GetDimNamesAttributeNode getDimNamesNode = GetDimNamesAttributeNode.create();
  @Child protected GetNamesAttributeNode getNamesNode = GetNamesAttributeNode.create();

  protected UnaryCopyAttributesNode(boolean copyAllAttributes) {
    this.copyAllAttributes = copyAllAttributes;
  }

  public static UnaryCopyAttributesNode create() {
    return UnaryCopyAttributesNodeGen.create(true);
  }

  public abstract RAbstractVector execute(RAbstractVector target, RAbstractVector left);

  protected boolean containsMetadata(RAbstractVector vector) {
    return vector instanceof RVector && hasDimNode.execute(vector)
        || (copyAllAttributes && vector.getAttributes() != null)
        || getNamesNode.getNames(vector) != null
        || getDimNamesNode.getDimNames(vector) != null;
  }

  @SuppressWarnings("unused")
  @Specialization(guards = "!containsMetadata(source)")
  protected RAbstractVector copyNoMetadata(RAbstractVector target, RAbstractVector source) {
    return target;
  }

  @SuppressWarnings("unused")
  @Specialization(guards = {"copyAllAttributes", "target == source"})
  protected RAbstractVector copySameVector(RAbstractVector target, RAbstractVector source) {
    return target;
  }

  @Specialization(guards = {"!copyAllAttributes || target != source", "containsMetadata(source)"})
  protected RAbstractVector copySameLength(
      RAbstractVector target,
      RAbstractVector source, //
      @Cached("create()") CopyOfRegAttributesNode copyOfReg, //
      @Cached("createDim()") RemoveFixedAttributeNode removeDim, //
      @Cached("createDimNames()") RemoveFixedAttributeNode removeDimNames, //
      @Cached("create()") InitAttributesNode initAttributes, //
      @Cached("createNames()") SetFixedAttributeNode putNames, //
      @Cached("createDim()") SetFixedAttributeNode putDim, //
      @Cached("createDimNames()") SetFixedAttributeNode putDimNames, //
      @Cached("createBinaryProfile()") ConditionProfile noDimensions, //
      @Cached("createBinaryProfile()") ConditionProfile hasNamesSource, //
      @Cached("createBinaryProfile()") ConditionProfile hasDimNames,
      @Cached("create()") GetDimAttributeNode getDimsNode) {
    RVector<?> result = target.materialize();

    if (copyAllAttributes) {
      copyOfReg.execute(source, result);
    }

    int[] newDimensions = getDimsNode.getDimensions(source);
    if (noDimensions.profile(newDimensions == null)) {
      DynamicObject attributes = result.getAttributes();
      if (attributes != null) {
        removeDim.execute(attributes);
        removeDimNames.execute(attributes);
      }

      RStringVector vecNames = getNamesNode.getNames(source);
      if (hasNamesSource.profile(vecNames != null)) {
        putNames.execute(initAttributes.execute(result), vecNames);
        return result;
      }
      return result;
    }

    putDim.execute(
        initAttributes.execute(result),
        RDataFactory.createIntVector(newDimensions, RDataFactory.COMPLETE_VECTOR));

    RList newDimNames = getDimNamesNode.getDimNames(source);
    if (hasDimNames.profile(newDimNames != null)) {
      putDimNames.execute(result.getAttributes(), newDimNames);
      newDimNames.elementNamePrefix = RRuntime.DIMNAMES_LIST_ELEMENT_NAME_PREFIX;
      return result;
    }
    return result;
  }
}