All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.microbean.lang.visitor.SubstituteVisitor Maven / Gradle / Ivy

The newest version!
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
 *
 * Copyright © 2023–2024 microBean™.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.microbean.lang.visitor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import java.util.function.BiPredicate;

import javax.lang.model.element.TypeParameterElement;

import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;

import org.microbean.lang.TypeAndElementSource;
import org.microbean.lang.Equality;

final class SubstituteVisitor extends StructuralTypeMapping {


  /*
   * Instance fields.
   */


  private final Equality equality;

  private final SupertypeVisitor supertypeVisitor;

  private final List from;

  private final List to;


  /*
   * Constructors.
   */


  SubstituteVisitor(final TypeAndElementSource tes,
                    final Equality equality,
                    final SupertypeVisitor supertypeVisitor, // used only for intersection types
                    List from,
                    List to) {
    super(tes);
    this.equality = equality == null ? new Equality(false) : equality;
    this.supertypeVisitor = Objects.requireNonNull(supertypeVisitor, "supertypeVisitor");
    // https://github.com/openjdk/jdk/blob/jdk-20+12/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#L3321-L3322
    // "If lists have different length, discard leading elements of the longer list."
    int fromSize = from.size();
    int toSize = to.size();
    if (fromSize != toSize) {
      if (fromSize > toSize) {
        from = from.subList(fromSize - toSize, fromSize);
        fromSize = from.size();
      }
      if (toSize > fromSize) {
        to = to.subList(toSize - fromSize, toSize);
        toSize = to.size();
      }
      assert fromSize == toSize;
    }
    this.from = List.copyOf(from);
    this.to = List.copyOf(to);
  }


  /*
   * Instance methods.
   */


  final SubstituteVisitor with(final List from,
                               final List to) {
    if (from == this.from && to == this.to) {
      return this;
    }
    return new SubstituteVisitor(this.tes, this.equality, this.supertypeVisitor, from, to);
  }

  // https://github.com/openjdk/jdk/blob/jdk-20+12/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#L3382-L3411
  @Override
  public final ExecutableType visitExecutable(ExecutableType t, final Void x) {
    assert t.getKind() == TypeKind.EXECUTABLE;

    /*
      if (Type.containsAny(to, t.tvars)) {
          //perform alpha-renaming of free-variables in 't'
          //if 'to' types contain variables that are free in 't'
          List freevars = newInstances(t.tvars);
          t = new ForAll(freevars,
                         Types.this.subst(t.qtype, t.tvars, freevars));
      }
    */

    List typeVariables = t.getTypeVariables();
    if (anyMatches(this.to, typeVariables, this.equality::equals)) {
      t = new org.microbean.lang.type.ExecutableType(t, newInstances(typeVariables));
      typeVariables = t.getTypeVariables();
    }

    // Now do substitution on the type variable bounds themselves.
    final List visitedTypeVariables = this.visitUpperBoundsOf(typeVariables);

    // Visit the other "parts" of the method that are structurally relevant.
    ExecutableType visitedT = (ExecutableType)super.visitExecutable(t, x);

    if (typeVariables == visitedTypeVariables) {
      if (t == visitedT) {
        return t;
      }
    } else {
      visitedT = this.with(typeVariables, visitedTypeVariables).visitExecutable(visitedT, x);
    }
    return new org.microbean.lang.type.ExecutableType(visitedT, visitedTypeVariables);
  }

  @Override
  public final IntersectionType visitIntersection(final IntersectionType t, final Void x) {
    assert t.getKind() == TypeKind.INTERSECTION;
    final TypeMirror supertype = this.supertypeVisitor.visit(t, x); // (Returns t.getBounds().get(0).)
    final TypeMirror visitedSupertype = this.visit(supertype, x);
    final List interfaces = this.supertypeVisitor.interfacesVisitor().visit(t, x);
    final List visitedInterfaces = this.visit(interfaces, x);
    if (supertype == visitedSupertype && interfaces == visitedInterfaces) {
      return t;
    }
    final List list = new ArrayList<>(visitedInterfaces.size() + 1);
    list.add(visitedSupertype);
    list.addAll(visitedInterfaces);
    return new org.microbean.lang.type.IntersectionType(list);
  }

  // https://github.com/openjdk/jdk/blob/jdk-20+12/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#L3347-L3357
  @Override
  public final TypeMirror visitTypeVariable(final TypeVariable tv, final Void x) {
    assert tv.getKind() == TypeKind.TYPEVAR;
    if (!(tv instanceof org.microbean.lang.type.Capture)) {
      final int size = this.from.size();
      assert size == this.to.size();
      for (int i = 0; i < size; i++) {
        final TypeMirror f = this.from.get(i);
        assert f.getKind() == TypeKind.TYPEVAR;
        assert f instanceof TypeVariable;
        if (this.equality.equals(f, tv)) {
          final TypeMirror t = this.to.get(i);
          assert t.getKind() == TypeKind.TYPEVAR;
          assert t instanceof TypeVariable;
          return t;
        }
      }
    }
    return tv;
  }

  // https://github.com/openjdk/jdk/blob/jdk-20+12/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#L3373-L3380
  @Override
  public final WildcardType visitWildcard(final WildcardType wt, final Void x) {
    assert wt.getKind() == TypeKind.WILDCARD;
    final TypeMirror extendsBound = wt.getExtendsBound();
    final TypeMirror superBound = wt.getSuperBound();
    if (superBound == null) {
      if (extendsBound == null) {
        // Unbounded.  No need to do anything else.
        return wt;
      }
      // Upper-bounded.
      final TypeMirror visitedExtendsBound = this.visit(extendsBound); // RECURSIVE
      if (extendsBound == visitedExtendsBound) {
        return wt;
      }
      return org.microbean.lang.type.WildcardType.upperBoundedWildcardType(visitedExtendsBound);
    } else if (extendsBound == null) {
      // Lower-bounded.
      final TypeMirror visitedSuperBound = this.visit(superBound); // RECURSIVE
      if (superBound == visitedSuperBound) {
        return wt;
      }
      return org.microbean.lang.type.WildcardType.lowerBoundedWildcardType(visitedSuperBound);
    } else {
      // Wildcards can only specify a single bound, either upper or lower.
      throw new IllegalArgumentException("wt: " + wt);
    }
  }

  // A port/translation of Types#newInstances(List).  "perform alpha-renaming of free-variables in 't' [free variables
  // in tvs]"
  //
  // This is a slavish port for the most part. There are several cases where we could make things more efficient but the
  // undocumented semantics might be lost.
  //
  // https://github.com/openjdk/jdk/blob/jdk-20+12/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#L3489-L3507
  private final List newInstances(final List tvs) {
    assert !tvs.isEmpty(); // shouldn't be called if so

    final List visitedTvs = new ArrayList<>(tvs);
    final SubstituteVisitor visitor = this.with(tvs, visitedTvs);
    for (int i = 0; i < visitedTvs.size(); i++) {

      // Get the existing type variable at position i.
      final TypeVariable tv = visitedTvs.get(i);

      final TypeMirror upperBound = tv.getUpperBound();

      // Compute a new upper bound for it.  Note the usage of tvs and visitedTvs in the from and to slots of the visitor
      // respectively.
      final TypeMirror visitedUpperBound = visitor.visit(upperBound); // RECURSIVE

      // It *seems* that if upperBound and visitedUpperBound are identical we wouldn't have to do anything below. But
      // the compiler's version of this method always creates a new instance, and the method is named newInstances, so
      // we do too.

      // Create a new TypeVariable whose upper bound is the just-visited upper bound.
      final org.microbean.lang.type.TypeVariable newTv =
        new org.microbean.lang.type.TypeVariable(this.tes, visitedUpperBound, tv.getLowerBound());
      newTv.setDefiningElement((TypeParameterElement)tv.asElement());

      // Replace the type variable at position i (the very one we're looking at) with the new, newly-bounded type
      // variable.
      visitedTvs.set(i, newTv);
    }
    return Collections.unmodifiableList(visitedTvs);
  }

  // https://github.com/openjdk/jdk/blob/3cd3a83647297f525f5eab48ce688e024ca6b08c/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#L3413-L3452
  private final List visitUpperBoundsOf(final List tvs) {
    if (tvs.isEmpty()) {
      return tvs; // preserve identity whenever possible
    }

    // Phase 1 (it appears): effectively call visit() on the upper bound of all the type variables. If this didn't
    // result in any changes, we're done.
    final List visitedUpperBounds = new ArrayList<>(tvs.size()); // could be pointless if nothing changed
    boolean changed = false;
    for (final TypeVariable tv : tvs) {
      final TypeMirror upperBound = tv.getUpperBound();
      final TypeMirror visitedUpperBound = this.visit(upperBound);
      visitedUpperBounds.add(visitedUpperBound); // this could end up being pointless if changed is false; nothing for it
      if (!changed && upperBound != visitedUpperBound) {
        changed = true;
      }
    }
    if (!changed) {
      return tvs; // preserve identity whenever possible
    }

    // Phase 2: Create a list of type variables temporarily without upper bounds. We will give them upper bounds in
    // phase 3.
    final List newTvs = new ArrayList<>(tvs.size());
    for (final TypeVariable tv : tvs) {
      final org.microbean.lang.type.TypeVariable newTv =
        new org.microbean.lang.type.TypeVariable(this.tes, null, tv.getLowerBound());
      newTv.setDefiningElement((TypeParameterElement)tv.asElement());
      newTvs.add(newTv);
    }

    // (All Lists at this point are the same size.)

    // Phase 3: Perform substitution over the visited (phase 1) upper bounds of the caller-supplied type variables with
    // the un-upper-bounded type variable "prototypes" created in phase 2. (This will result in type variables that have
    // no upper bound yet, but we have them in a list, and so can doctor them in phase 4 below.)
    final SubstituteVisitor visitor = this.with(tvs, newTvs);
    for (int i = 0; i < visitedUpperBounds.size(); i++) {
      visitedUpperBounds.set(i, visitor.visit(visitedUpperBounds.get(i)));
    }

    // Phase 4: Replace the unspecified upper bounds (see phase 2) with the substituted bounds we calculated in phase 1
    // and doctored in phase 3.
    for (int i = 0; i < newTvs.size(); i++) {
      final org.microbean.lang.type.TypeVariable upperBoundlessTv = newTvs.get(i);
      final org.microbean.lang.type.TypeVariable newTv =
        new org.microbean.lang.type.TypeVariable(this.tes, visitedUpperBounds.get(i), upperBoundlessTv.getLowerBound());
      newTv.setDefiningElement(upperBoundlessTv.asElement());
      newTvs.set(i, newTv);
    }

    return Collections.unmodifiableList(newTvs);
  }


  /*
   * Static methods.
   */


  // Does at least one t in ts "match" at least one s in ss according to p?
  private static final boolean anyMatches(final Iterable ts,
                                          final Collection ss,
                                          final BiPredicate p) {
    for (final TypeMirror t : ts) {
      for (final TypeMirror s : ss) {
        if (p.test(t, s)) {
          return true;
        }
      }
    }
    return false;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy