blob: 9a5f7639259b5898c73f85194ad63fb725fd02af [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* 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 com.google.turbine.binder;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.bound.HeaderBoundClass;
import com.google.turbine.binder.bound.PackageSourceBoundClass;
import com.google.turbine.binder.bound.SourceHeaderBoundClass;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.env.LazyEnv.LazyBindingError;
import com.google.turbine.binder.lookup.LookupKey;
import com.google.turbine.binder.lookup.LookupResult;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.diag.TurbineLog.TurbineLogWithSource;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.ClassTy;
import java.util.ArrayDeque;
import org.jspecify.nullness.Nullable;
/** Type hierarchy binding. */
public class HierarchyBinder {
/** Binds the type hierarchy (superclasses and interfaces) for a single class. */
public static SourceHeaderBoundClass bind(
TurbineLogWithSource log,
ClassSymbol origin,
PackageSourceBoundClass base,
Env<ClassSymbol, ? extends HeaderBoundClass> env) {
return new HierarchyBinder(log, origin, base, env).bind();
}
private final TurbineLogWithSource log;
private final ClassSymbol origin;
private final PackageSourceBoundClass base;
private final Env<ClassSymbol, ? extends HeaderBoundClass> env;
private HierarchyBinder(
TurbineLogWithSource log,
ClassSymbol origin,
PackageSourceBoundClass base,
Env<ClassSymbol, ? extends HeaderBoundClass> env) {
this.log = log;
this.origin = origin;
this.base = base;
this.env = env;
}
private SourceHeaderBoundClass bind() {
Tree.TyDecl decl = base.decl();
ClassSymbol superclass;
if (decl.xtnds().isPresent()) {
superclass = resolveClass(decl.xtnds().get());
if (origin.equals(superclass)) {
log.error(decl.xtnds().get().position(), ErrorKind.CYCLIC_HIERARCHY, origin);
}
} else {
switch (decl.tykind()) {
case ENUM:
superclass = ClassSymbol.ENUM;
break;
case INTERFACE:
case ANNOTATION:
case CLASS:
superclass = !origin.equals(ClassSymbol.OBJECT) ? ClassSymbol.OBJECT : null;
break;
case RECORD:
superclass = ClassSymbol.RECORD;
break;
default:
throw new AssertionError(decl.tykind());
}
}
ImmutableList.Builder<ClassSymbol> interfaces = ImmutableList.builder();
if (!decl.impls().isEmpty()) {
for (Tree.ClassTy i : decl.impls()) {
ClassSymbol result = resolveClass(i);
if (result == null) {
continue;
}
if (origin.equals(result)) {
log.error(i.position(), ErrorKind.CYCLIC_HIERARCHY, origin);
}
interfaces.add(result);
}
} else {
if (decl.tykind() == TurbineTyKind.ANNOTATION) {
interfaces.add(ClassSymbol.ANNOTATION);
}
}
ImmutableMap.Builder<String, TyVarSymbol> typeParameters = ImmutableMap.builder();
for (Tree.TyParam p : decl.typarams()) {
typeParameters.put(p.name().value(), new TyVarSymbol(origin, p.name().value()));
}
return new SourceHeaderBoundClass(base, superclass, interfaces.build(), typeParameters.build());
}
/**
* Resolves the {@link ClassSymbol} for the given {@link Tree.ClassTy}, with handling for
* non-canonical qualified type names.
*/
private @Nullable ClassSymbol resolveClass(Tree.ClassTy ty) {
// flatten a left-recursive qualified type name to its component simple names
// e.g. Foo<Bar>.Baz -> ["Foo", "Bar"]
ArrayDeque<Tree.Ident> flat = new ArrayDeque<>();
for (Tree.ClassTy curr = ty; curr != null; curr = curr.base().orElse(null)) {
flat.addFirst(curr.name());
}
// Resolve the base symbol in the qualified name.
LookupResult result = lookup(ty, new LookupKey(ImmutableList.copyOf(flat)));
if (result == null) {
log.error(ty.position(), ErrorKind.CANNOT_RESOLVE, Joiner.on('.').join(flat));
return null;
}
// Resolve pieces in the qualified name referring to member types.
// This needs to consider member type declarations inherited from supertypes and interfaces.
ClassSymbol sym = (ClassSymbol) result.sym();
for (Tree.Ident bit : result.remaining()) {
sym = resolveNext(ty, sym, bit);
if (sym == null) {
break;
}
}
return sym;
}
private @Nullable ClassSymbol resolveNext(ClassTy ty, ClassSymbol sym, Tree.Ident bit) {
ClassSymbol next;
try {
next = Resolve.resolve(env, origin, sym, bit);
} catch (LazyBindingError e) {
log.error(ty.position(), ErrorKind.CYCLIC_HIERARCHY, e.getMessage());
return null;
}
if (next == null) {
log.error(
bit.position(),
ErrorKind.SYMBOL_NOT_FOUND,
new ClassSymbol(sym.binaryName() + '$' + bit));
}
return next;
}
/** Resolve a qualified type name to a symbol. */
private @Nullable LookupResult lookup(Tree tree, LookupKey lookup) {
// Handle any lexically enclosing class declarations (if we're binding a member class).
// We could build out scopes for this, but it doesn't seem worth it. (And sharing the scopes
// with other members of the same enclosing declaration would be complicated.)
for (ClassSymbol curr = base.owner(); curr != null; curr = env.getNonNull(curr).owner()) {
ClassSymbol result;
try {
result = Resolve.resolve(env, origin, curr, lookup.first());
} catch (LazyBindingError e) {
log.error(tree.position(), ErrorKind.CYCLIC_HIERARCHY, e.getMessage());
result = null;
}
if (result != null) {
return new LookupResult(result, lookup);
}
}
// Fall back to the top-level scopes for the compilation unit (imports, same package, then
// qualified name resolution).
return base.scope().lookup(lookup, Resolve.resolveFunction(env, origin));
}
}