Skip to content

Commit

Permalink
feat: simplify union types containing type parameter types disregardi…
Browse files Browse the repository at this point in the history
…ng entry order (#894)

### Summary of Changes

Consider the `class C<T> where { T sub Any }`. Previously, the type
`union<T, Any>` was simplified to `T`, while the type `union<Any, T>`
was simplified to `Any`. Now the simplified type is `T` in both cases.
We chose the type parameter type, since it can be narrower if it has a
lower bound.
  • Loading branch information
lars-reimann authored Feb 18, 2024
1 parent 79d611d commit cf6e77e
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 147 deletions.
120 changes: 61 additions & 59 deletions packages/safe-ds-lang/src/language/typing/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export type TypeParameterSubstitutions = Map<SdsTypeParameter, Type>;
*/
export abstract class Type {
/**
* Whether this type allows `null` as a value.
* Whether this type is explicitly marked as nullable (e.g. using a `?` for named types). A type parameter type can
* also become nullable if its upper bound is nullable, which is not covered here. Use {@link TypeChecker.canBeNull}
* if you need to cover this.
*/
abstract isNullable: boolean;
abstract isExplicitlyNullable: boolean;

/**
* Returns whether the type is equal to another type.
Expand All @@ -47,11 +49,11 @@ export abstract class Type {
/**
* Returns a copy of this type with the given nullability.
*/
abstract updateNullability(isNullable: boolean): Type;
abstract updateExplicitNullability(isExplicitlyNullable: boolean): Type;
}

export class CallableType extends Type {
override isNullable: boolean = false;
override isExplicitlyNullable: boolean = false;

constructor(
readonly callable: SdsCallable,
Expand Down Expand Up @@ -114,8 +116,8 @@ export class CallableType extends Type {
);
}

override updateNullability(isNullable: boolean): Type {
if (!isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): Type {
if (!isExplicitlyNullable) {
return this;
}

Expand All @@ -125,20 +127,20 @@ export class CallableType extends Type {

export class LiteralType extends Type {
readonly constants: Constant[];
private _isNullable: boolean | undefined;
private _isExplicitlyNullable: boolean | undefined;

constructor(...constants: Constant[]) {
super();

this.constants = constants;
}

override get isNullable(): boolean {
if (this._isNullable === undefined) {
this._isNullable = this.constants.some((it) => it === NullConstant);
override get isExplicitlyNullable(): boolean {
if (this._isExplicitlyNullable === undefined) {
this._isExplicitlyNullable = this.constants.some((it) => it === NullConstant);
}

return this._isNullable;
return this._isExplicitlyNullable;
}

override equals(other: unknown): boolean {
Expand Down Expand Up @@ -166,10 +168,10 @@ export class LiteralType extends Type {
return this;
}

override updateNullability(isNullable: boolean): LiteralType {
if (this.isNullable && !isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): LiteralType {
if (this.isExplicitlyNullable && !isExplicitlyNullable) {
return new LiteralType(...this.constants.filter((it) => it !== NullConstant));
} else if (!this.isNullable && isNullable) {
} else if (!this.isExplicitlyNullable && isExplicitlyNullable) {
return new LiteralType(...this.constants, NullConstant);
} else {
return this;
Expand All @@ -179,7 +181,7 @@ export class LiteralType extends Type {

export class NamedTupleType<T extends SdsDeclaration> extends Type {
readonly entries: NamedTupleEntry<T>[];
override readonly isNullable = false;
override readonly isExplicitlyNullable = false;

constructor(...entries: NamedTupleEntry<T>[]) {
super();
Expand Down Expand Up @@ -237,8 +239,8 @@ export class NamedTupleType<T extends SdsDeclaration> extends Type {
return new NamedTupleType(...this.entries.map((it) => it.unwrap()));
}

override updateNullability(isNullable: boolean): Type {
if (!isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): Type {
if (!isExplicitlyNullable) {
return this;
}

Expand Down Expand Up @@ -289,14 +291,14 @@ export abstract class NamedType<T extends SdsDeclaration> extends Type {
}

override toString(): string {
if (this.isNullable) {
if (this.isExplicitlyNullable) {
return `${this.declaration.name}?`;
} else {
return this.declaration.name;
}
}

abstract override updateNullability(isNullable: boolean): NamedType<T>;
abstract override updateExplicitNullability(isExplicitlyNullable: boolean): NamedType<T>;

unwrap(): NamedType<T> {
return this;
Expand All @@ -307,7 +309,7 @@ export class ClassType extends NamedType<SdsClass> {
constructor(
declaration: SdsClass,
readonly substitutions: TypeParameterSubstitutions,
override readonly isNullable: boolean,
override readonly isExplicitlyNullable: boolean,
) {
super(declaration);
}
Expand All @@ -330,7 +332,7 @@ export class ClassType extends NamedType<SdsClass> {

return (
other.declaration === this.declaration &&
other.isNullable === this.isNullable &&
other.isExplicitlyNullable === this.isExplicitlyNullable &&
substitutionsAreEqual(other.substitutions, this.substitutions)
);
}
Expand All @@ -344,7 +346,7 @@ export class ClassType extends NamedType<SdsClass> {
.join(', ')}>`;
}

if (this.isNullable) {
if (this.isExplicitlyNullable) {
result += '?';
}

Expand All @@ -360,27 +362,27 @@ export class ClassType extends NamedType<SdsClass> {
stream(this.substitutions).map(([key, value]) => [key, value.substituteTypeParameters(substitutions)]),
);

return new ClassType(this.declaration, newSubstitutions, this.isNullable);
return new ClassType(this.declaration, newSubstitutions, this.isExplicitlyNullable);
}

override unwrap(): ClassType {
const newSubstitutions = new Map(stream(this.substitutions).map(([key, value]) => [key, value.unwrap()]));
return new ClassType(this.declaration, newSubstitutions, this.isNullable);
return new ClassType(this.declaration, newSubstitutions, this.isExplicitlyNullable);
}

override updateNullability(isNullable: boolean): ClassType {
if (this.isNullable === isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): ClassType {
if (this.isExplicitlyNullable === isExplicitlyNullable) {
return this;
}

return new ClassType(this.declaration, this.substitutions, isNullable);
return new ClassType(this.declaration, this.substitutions, isExplicitlyNullable);
}
}

export class EnumType extends NamedType<SdsEnum> {
constructor(
declaration: SdsEnum,
override readonly isNullable: boolean,
override readonly isExplicitlyNullable: boolean,
) {
super(declaration);
}
Expand All @@ -392,26 +394,26 @@ export class EnumType extends NamedType<SdsEnum> {
return false;
}

return other.declaration === this.declaration && other.isNullable === this.isNullable;
return other.declaration === this.declaration && other.isExplicitlyNullable === this.isExplicitlyNullable;
}

override substituteTypeParameters(_substitutions: TypeParameterSubstitutions): Type {
return this;
}

override updateNullability(isNullable: boolean): EnumType {
if (this.isNullable === isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): EnumType {
if (this.isExplicitlyNullable === isExplicitlyNullable) {
return this;
}

return new EnumType(this.declaration, isNullable);
return new EnumType(this.declaration, isExplicitlyNullable);
}
}

export class EnumVariantType extends NamedType<SdsEnumVariant> {
constructor(
declaration: SdsEnumVariant,
override readonly isNullable: boolean,
override readonly isExplicitlyNullable: boolean,
) {
super(declaration);
}
Expand All @@ -423,26 +425,26 @@ export class EnumVariantType extends NamedType<SdsEnumVariant> {
return false;
}

return other.declaration === this.declaration && other.isNullable === this.isNullable;
return other.declaration === this.declaration && other.isExplicitlyNullable === this.isExplicitlyNullable;
}

override substituteTypeParameters(_substitutions: TypeParameterSubstitutions): Type {
return this;
}

override updateNullability(isNullable: boolean): EnumVariantType {
if (this.isNullable === isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): EnumVariantType {
if (this.isExplicitlyNullable === isExplicitlyNullable) {
return this;
}

return new EnumVariantType(this.declaration, isNullable);
return new EnumVariantType(this.declaration, isExplicitlyNullable);
}
}

export class TypeParameterType extends NamedType<SdsTypeParameter> {
constructor(
declaration: SdsTypeParameter,
override readonly isNullable: boolean,
override readonly isExplicitlyNullable: boolean,
) {
super(declaration);
}
Expand All @@ -454,35 +456,35 @@ export class TypeParameterType extends NamedType<SdsTypeParameter> {
return false;
}

return other.declaration === this.declaration && other.isNullable === this.isNullable;
return other.declaration === this.declaration && other.isExplicitlyNullable === this.isExplicitlyNullable;
}

override substituteTypeParameters(substitutions: TypeParameterSubstitutions): Type {
const substitution = substitutions.get(this.declaration);

if (!substitution) {
return this;
} else if (this.isNullable) {
return substitution.updateNullability(true);
} else if (this.isExplicitlyNullable) {
return substitution.updateExplicitNullability(true);
} else {
return substitution;
}
}

override updateNullability(isNullable: boolean): TypeParameterType {
if (this.isNullable === isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): TypeParameterType {
if (this.isExplicitlyNullable === isExplicitlyNullable) {
return this;
}

return new TypeParameterType(this.declaration, isNullable);
return new TypeParameterType(this.declaration, isExplicitlyNullable);
}
}

/**
* A type that represents an actual class, enum, or enum variant instead of an instance of it.
*/
export class StaticType extends Type {
override readonly isNullable = false;
override readonly isExplicitlyNullable = false;

constructor(readonly instanceType: NamedType<SdsDeclaration>) {
super();
Expand Down Expand Up @@ -512,8 +514,8 @@ export class StaticType extends Type {
return this;
}

override updateNullability(isNullable: boolean): Type {
if (!isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): Type {
if (!isExplicitlyNullable) {
return this;
}

Expand All @@ -523,20 +525,20 @@ export class StaticType extends Type {

export class UnionType extends Type {
readonly possibleTypes: Type[];
private _isNullable: boolean | undefined;
private _isExplicitlyNullable: boolean | undefined;

constructor(...possibleTypes: Type[]) {
super();

this.possibleTypes = possibleTypes;
}

override get isNullable(): boolean {
if (this._isNullable === undefined) {
this._isNullable = this.possibleTypes.some((it) => it.isNullable);
override get isExplicitlyNullable(): boolean {
if (this._isExplicitlyNullable === undefined) {
this._isExplicitlyNullable = this.possibleTypes.some((it) => it.isExplicitlyNullable);
}

return this._isNullable;
return this._isExplicitlyNullable;
}

override equals(other: unknown): boolean {
Expand Down Expand Up @@ -583,14 +585,14 @@ export class UnionType extends Type {
return new UnionType(...newPossibleTypes);
}

override updateNullability(isNullable: boolean): Type {
if (this.isNullable && !isNullable) {
return new UnionType(...this.possibleTypes.map((it) => it.updateNullability(false)));
} else if (!this.isNullable && isNullable) {
override updateExplicitNullability(isExplicitlyNullable: boolean): Type {
if (this.isExplicitlyNullable && !isExplicitlyNullable) {
return new UnionType(...this.possibleTypes.map((it) => it.updateExplicitNullability(false)));
} else if (!this.isExplicitlyNullable && isExplicitlyNullable) {
if (isEmpty(this.possibleTypes)) {
return new LiteralType(NullConstant);
} else {
return new UnionType(...this.possibleTypes.map((it) => it.updateNullability(true)));
return new UnionType(...this.possibleTypes.map((it) => it.updateExplicitNullability(true)));
}
} else {
return this;
Expand All @@ -599,7 +601,7 @@ export class UnionType extends Type {
}

class UnknownTypeClass extends Type {
readonly isNullable = false;
readonly isExplicitlyNullable = false;

override equals(other: unknown): boolean {
return other instanceof UnknownTypeClass;
Expand All @@ -617,7 +619,7 @@ class UnknownTypeClass extends Type {
return this;
}

override updateNullability(_isNullable: boolean): Type {
override updateExplicitNullability(_isExplicitlyNullable: boolean): Type {
return this;
}
}
Expand Down
Loading

0 comments on commit cf6e77e

Please sign in to comment.