Skip to content

Commit

Permalink
GH-2111: As a developer I'd like to use type aliases as values (esp. …
Browse files Browse the repository at this point in the history
…in JSX tags) -- preparations (#2112)

* improve handling of IdentifierRefs pointing to type aliases
  • Loading branch information
mor-n4 authored Apr 13, 2021
1 parent 1fae8f2 commit 14642ce
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import org.eclipse.n4js.ts.types.TStructMember;
import org.eclipse.n4js.ts.types.TStructuralType;
import org.eclipse.n4js.ts.types.Type;
import org.eclipse.n4js.ts.types.TypeAlias;
import org.eclipse.n4js.ts.types.TypeVariable;
import org.eclipse.n4js.ts.types.TypesFactory;
import org.eclipse.n4js.ts.types.TypingStrategy;
Expand Down Expand Up @@ -154,6 +155,14 @@ public static TypeRef wrapTypeInTypeRef(Type type, TypeArgument... typeArgs) {
return createTypeTypeRef(createTypeRef(type, typeArgs), false);
} else if (type instanceof TObjectPrototype) {
return createConstructorTypeRef(type, typeArgs);
} else if (type instanceof TypeAlias) {
Type actualDeclType = getActualDeclaredType((TypeAlias) type);
if (actualDeclType != null) {
TypeRef result = wrapTypeInTypeRef(actualDeclType);
result.setOriginalAliasTypeRef(createTypeRef(type, typeArgs));
return result;
}
return createTypeTypeRef(createTypeRef(type, typeArgs), false);
} else {
return createTypeRef(type, typeArgs);
}
Expand Down Expand Up @@ -828,6 +837,34 @@ public static TypeVariableMapping createTypeVariableMapping(TypeVariable typeVar
return result;
}

/**
* Returns the {@link TypeRef#getDeclaredType() declared type} of the aliased / actual type of the given type alias
* or <code>null</code> if the aliased / actual type does not have a declared type (e.g. in case of a type alias
* such as <code>type A = Cls1 | Cls2;</code>) or in case of an invalid type model.
* <p>
* Resolves chains of type aliases and therefore <em>never</em> returns a value of type {@link TypeAlias}.
*/
public static Type getActualDeclaredType(TypeAlias alias) {
RecursionGuard<Type> guard = null;
Type currType = alias;
while (true) {
TypeRef actualTypeRef = ((TypeAlias) currType).getTypeRef();
currType = actualTypeRef != null ? actualTypeRef.getDeclaredType() : null;
if (!(currType instanceof TypeAlias)) {
break;
}
if (guard == null) {
guard = new RecursionGuard<>();
}
if (!guard.tryNext(currType)) {
// cyclic type alias declaration
currType = null;
break;
}
}
return currType;
}

/**
* Type references may be nested within other type references, e.g. the members of a union type. This method will
* return the outermost type reference that contains the given type reference but is not itself contained in another
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
import org.eclipse.n4js.ts.types.TTypedElement;
import org.eclipse.n4js.ts.types.TypableElement;
import org.eclipse.n4js.ts.types.Type;
import org.eclipse.n4js.ts.types.TypeAlias;
import org.eclipse.n4js.ts.types.TypesPackage;
import org.eclipse.n4js.ts.types.TypingStrategy;
import org.eclipse.n4js.ts.types.util.TypesSwitch;
Expand Down Expand Up @@ -250,6 +251,15 @@ public TypeRef caseType(Type type) {
return TypeUtils.wrapTypeInTypeRef(type);
}

/**
* This override is required only to solve the ambiguity due to {@link TypeAlias} being a subtype of both
* {@link Type} and {@link TTypedElement}. We choose to treat it as a {@code Type}.
*/
@Override
public TypeRef caseTypeAlias(TypeAlias typeAlias) {
return this.caseType(typeAlias);
}

@Override
public TypeRef caseTEnumLiteral(TEnumLiteral enumLiteral) {
return ref((TEnum) enumLiteral.eContainer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ class N4JSXValidator extends AbstractN4JSDeclarativeValidator {
def public void checkReactElementBinding(JSXElement jsxElem) {
val expr = jsxElem.jsxElementName.expression;
val TypeRef exprTypeRef = reactHelper.getJsxElementBindingType(jsxElem);
var isFunction = exprTypeRef instanceof FunctionTypeExprOrRef;
var isClass = exprTypeRef instanceof TypeTypeRef && (exprTypeRef as TypeTypeRef).constructorRef;
val isFunction = exprTypeRef instanceof FunctionTypeExprOrRef;
val isClass = exprTypeRef instanceof TypeTypeRef && (exprTypeRef as TypeTypeRef).constructorRef;

if (!isFunction && !isClass) {
val String refName = expr.refName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2021 NumberFour AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* NumberFour AG - Initial API and implementation
*/

/* XPECT_SETUP org.eclipse.n4js.spec.tests.N4JSSpecTest END_SETUP */


function foo(p: constructor{?}) {}

class Cls {}

type A = Cls;

// XPECT errors --> "A type alias may not be used as a value." at "A"
A;
// XPECT errors --> "A type alias may not be used as a value." at "A"
new A();
// XPECT errors --> "A type alias may not be used as a value." at "A"
foo(A);
// XPECT errors ---
// "A type alias may not be used as a value." at "A"
// "Cannot resolve JSX implementation." at "<A/>"
// ---
<A/>;

// note: the above also tests that we do not get unnecessary duplicate error messages.

0 comments on commit 14642ce

Please sign in to comment.