Skip to content

Commit

Permalink
feat: improvements to constraints (#896)
Browse files Browse the repository at this point in the history
Closes partially #18

### Summary of Changes

* Remove lower type parameter bounds. We don't have a use case for them
and they vastly complicated the implementation. For instance, `Nothing`
is no subtype of a type parameter type with a lower bound other than
`Nothing`. This means, when computing the highest common subtype (#860),
we could only return `$unknown` for a type parameter type with a lower
bound of, say, `Int`, and another type like `Number`.
* Specify the upper bound of a type parameter directly where it's
declared. Since they cannot also have a lower bound anymore and
previously could only have a single, acyclic upper bound, we don't lose
additional expressiveness this way. Previous type parameters may be used
as the upper bound of later ones. The scope provider prevents forward
references.
* Add syntax, formatting and scoping for parameter bounds (#18). We are
doing this in this PR, since we would otherwise not have constraints to
test constraint lists with.
  • Loading branch information
lars-reimann authored Feb 19, 2024
1 parent 7e0dc3f commit b81bef9
Show file tree
Hide file tree
Showing 81 changed files with 668 additions and 1,654 deletions.
1 change: 0 additions & 1 deletion docs/lexer/safe_ds_lexer/_safe_ds_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"not",
"or",
"sub",
"super",
)

builtins = (
Expand Down
42 changes: 13 additions & 29 deletions packages/safe-ds-lang/src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -378,23 +378,19 @@ SdsConstraintList returns SdsConstraintList:
interface SdsConstraint extends SdsObject {}

SdsConstraint returns SdsConstraint:
SdsTypeParameterBound
SdsParameterBound
;

interface SdsTypeParameterBound extends SdsConstraint {
leftOperand?: @SdsTypeParameter
interface SdsParameterBound extends SdsConstraint {
leftOperand?: @SdsParameter
operator: string
rightOperand: SdsType
rightOperand: SdsExpression
}

SdsTypeParameterBound returns SdsTypeParameterBound:
leftOperand=[SdsTypeParameter:ID]
operator=SdsTypeParameterBoundOperator
rightOperand=SdsType
;

SdsTypeParameterBoundOperator returns string:
'sub' | 'super'
SdsParameterBound returns SdsParameterBound:
leftOperand=[SdsParameter:ID]
operator=SdsComparisonOperator
rightOperand=SdsExpression
;


Expand Down Expand Up @@ -967,11 +963,7 @@ SdsUnionTypeArgumentList returns SdsTypeArgumentList:
;

SdsUnionTypeArgument returns SdsTypeArgument:
value=SdsUnionTypeArgumentValue
;

SdsUnionTypeArgumentValue returns SdsTypeArgumentValue:
{SdsTypeProjection} ^type=SdsType
value=SdsType
;

SdsParentType returns SdsType:
Expand All @@ -995,13 +987,15 @@ SdsTypeParameterList returns SdsTypeParameterList:

interface SdsTypeParameter extends SdsNamedTypeDeclaration {
variance?: string
upperBound?: SdsType
defaultValue?: SdsType
}

SdsTypeParameter returns SdsTypeParameter:
annotationCalls+=SdsAnnotationCall*
variance=SdsTypeParameterVariance?
name=ID
('sub' upperBound=SdsType)?
('=' defaultValue=SdsType)?
;

Expand All @@ -1022,22 +1016,12 @@ SdsTypeArgumentList returns SdsTypeArgumentList:

interface SdsTypeArgument extends SdsObject {
typeParameter?: @SdsTypeParameter
value: SdsTypeArgumentValue
value: SdsType
}

SdsTypeArgument returns SdsTypeArgument:
(typeParameter=[SdsTypeParameter:ID] '=' )?
value=SdsTypeArgumentValue
;

interface SdsTypeArgumentValue extends SdsObject {}

interface SdsTypeProjection extends SdsTypeArgumentValue {
^type: SdsType
}

SdsTypeArgumentValue returns SdsTypeArgumentValue:
{SdsTypeProjection} ^type=SdsType
value=SdsType
;


Expand Down
66 changes: 0 additions & 66 deletions packages/safe-ds-lang/src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
isSdsClass,
isSdsDeclaration,
isSdsEnum,
isSdsEnumVariant,
isSdsFunction,
isSdsLambda,
isSdsModule,
Expand All @@ -24,7 +23,6 @@ import {
isSdsSegment,
isSdsTypeArgumentList,
isSdsTypeParameter,
isSdsTypeParameterBound,
isSdsTypeParameterList,
SdsAbstractCall,
SdsAbstractResult,
Expand All @@ -42,7 +40,6 @@ import {
SdsClass,
SdsClassMember,
SdsColumn,
SdsConstraint,
SdsDeclaration,
SdsEnum,
SdsEnumVariant,
Expand All @@ -65,7 +62,6 @@ import {
SdsTypeArgument,
SdsTypeArgumentList,
SdsTypeParameter,
SdsTypeParameterBound,
SdsTypeParameterList,
} from '../generated/ast.js';

Expand Down Expand Up @@ -173,51 +169,6 @@ export namespace TypeParameter {
export const isInvariant = (node: SdsTypeParameter | undefined): boolean => {
return isSdsTypeParameter(node) && !node.variance;
};

export const getLowerBounds = (node: SdsTypeParameter | undefined): SdsTypeParameterBound[] => {
return getBounds(node).filter((it) => {
if (it.operator === 'super') {
// Type parameter is the left operand
return it.leftOperand?.ref === node;
} else if (it.operator === 'sub') {
// Type parameter is the right operand
return isSdsNamedType(it.rightOperand) && it.rightOperand.declaration?.ref === node;
} else {
/* c8 ignore next 2 */
return false;
}
});
};

export const getUpperBounds = (node: SdsTypeParameter | undefined): SdsTypeParameterBound[] => {
return getBounds(node).filter((it) => {
if (it.operator === 'sub') {
// Type parameter is the left operand
return it.leftOperand?.ref === node;
} else if (it.operator === 'super') {
// Type parameter is the right operand
return isSdsNamedType(it.rightOperand) && it.rightOperand.declaration?.ref === node;
} else {
/* c8 ignore next 2 */
return false;
}
});
};

const getBounds = (node: SdsTypeParameter | undefined): SdsTypeParameterBound[] => {
const declarationContainingTypeParameter = getContainerOfType(node?.$container, isSdsDeclaration);
return getConstraints(declarationContainingTypeParameter).filter((it) => {
if (!isSdsTypeParameterBound(it)) {
/* c8 ignore next 2 */
return false;
}

return (
it.leftOperand?.ref === node ||
(isSdsNamedType(it.rightOperand) && it.rightOperand.declaration?.ref === node)
);
}) as SdsTypeParameterBound[];
};
}

// -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -301,23 +252,6 @@ export const getColumns = (node: SdsSchema | undefined): SdsColumn[] => {
return node?.columnList?.columns ?? [];
};

export const getConstraints = (node: SdsDeclaration | undefined): SdsConstraint[] => {
if (isSdsAnnotation(node)) {
/* c8 ignore next 2 */
return node.constraintList?.constraints ?? [];
} else if (isSdsClass(node)) {
return node.constraintList?.constraints ?? [];
} else if (isSdsEnumVariant(node)) {
/* c8 ignore next 2 */
return node.constraintList?.constraints ?? [];
} else if (isSdsFunction(node)) {
return node.constraintList?.constraints ?? [];
} else {
/* c8 ignore next 2 */
return [];
}
};

export const getEnumVariants = (node: SdsEnum | undefined): SdsEnumVariant[] => {
return node?.body?.variants ?? [];
};
Expand Down
6 changes: 3 additions & 3 deletions packages/safe-ds-lang/src/language/lsp/safe-ds-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ export class SafeDsFormatter extends AbstractFormatter {
// -----------------------------------------------------------------------------
else if (ast.isSdsConstraintList(node)) {
this.formatSdsConstraintList(node);
} else if (ast.isSdsTypeParameterBound(node)) {
this.formatSdsTypeParameterBound(node);
} else if (ast.isSdsParameterBound(node)) {
this.formatSdsParameterBound(node);
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -529,7 +529,7 @@ export class SafeDsFormatter extends AbstractFormatter {
}
}

private formatSdsTypeParameterBound(node: ast.SdsTypeParameterBound) {
private formatSdsParameterBound(node: ast.SdsParameterBound) {
const formatter = this.getNodeFormatter(node);

formatter.property('operator').surround(oneSpace());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isSdsModule,
isSdsNamedType,
isSdsParameter,
isSdsParameterBound,
isSdsPipeline,
isSdsPlaceholder,
isSdsReference,
Expand All @@ -24,7 +25,6 @@ import {
isSdsSegment,
isSdsTypeArgument,
isSdsTypeParameter,
isSdsTypeParameterBound,
isSdsYield,
} from '../generated/ast.js';
import { SafeDsServices } from '../safe-ds-module.js';
Expand Down Expand Up @@ -91,6 +91,12 @@ export class SafeDsSemanticTokenProvider extends AbstractSemanticTokenProvider {
...info,
});
}
} else if (isSdsParameterBound(node)) {
acceptor({
node,
property: 'leftOperand',
type: SemanticTokenTypes.parameter,
});
} else if (isSdsReference(node)) {
const info = this.computeSemanticTokenInfoForDeclaration(node.target.ref);
if (info) {
Expand All @@ -108,12 +114,6 @@ export class SafeDsSemanticTokenProvider extends AbstractSemanticTokenProvider {
type: SemanticTokenTypes.typeParameter,
});
}
} else if (isSdsTypeParameterBound(node)) {
acceptor({
node,
property: 'leftOperand',
type: SemanticTokenTypes.typeParameter,
});
} else if (isSdsYield(node)) {
// For lack of a better option, we use the token type for parameters here
acceptor({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@ import {
PrecomputedScopes,
} from 'langium';
import {
isSdsAnnotation,
isSdsClass,
isSdsDeclaration,
isSdsEnum,
isSdsEnumVariant,
isSdsFunction,
isSdsModule,
isSdsParameter,
isSdsParameterList,
isSdsPipeline,
isSdsSegment,
isSdsTypeParameter,
isSdsTypeParameterList,
SdsClass,
SdsEnum,
SdsEnumVariant,
SdsParameter,
SdsTypeParameter,
} from '../generated/ast.js';

Expand All @@ -46,6 +50,8 @@ export class SafeDsScopeComputation extends DefaultScopeComputation {
this.processSdsEnum(node, document, scopes);
} else if (isSdsEnumVariant(node)) {
this.processSdsEnumVariant(node, document, scopes);
} else if (isSdsParameter(node)) {
this.processSdsParameter(node, document, scopes);
} else if (isSdsTypeParameter(node)) {
this.processSdsTypeParameter(node, document, scopes);
} else {
Expand Down Expand Up @@ -101,6 +107,32 @@ export class SafeDsScopeComputation extends DefaultScopeComputation {
this.addToScopesIfKeyIsDefined(scopes, node.constraintList, description);
}

private processSdsParameter(node: SdsParameter, document: LangiumDocument, scopes: PrecomputedScopes): void {
const containingCallable = getContainerOfType(node, isSdsParameterList)?.$container;
if (!containingCallable) {
/* c8 ignore next 2 */
return;
}

const name = this.nameProvider.getName(node);
if (!name) {
/* c8 ignore next 2 */
return;
}

const description = this.descriptions.createDescription(node, name, document);

if (isSdsAnnotation(containingCallable)) {
this.addToScopesIfKeyIsDefined(scopes, containingCallable.constraintList, description);
} else if (isSdsClass(containingCallable)) {
this.addToScopesIfKeyIsDefined(scopes, containingCallable.constraintList, description);
} else if (isSdsEnumVariant(containingCallable)) {
this.addToScopesIfKeyIsDefined(scopes, containingCallable.constraintList, description);
} else if (isSdsFunction(containingCallable)) {
this.addToScopesIfKeyIsDefined(scopes, containingCallable.constraintList, description);
}
}

private processSdsTypeParameter(
node: SdsTypeParameter,
document: LangiumDocument,
Expand All @@ -123,12 +155,10 @@ export class SafeDsScopeComputation extends DefaultScopeComputation {
if (isSdsClass(containingDeclaration)) {
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.parameterList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.parentTypeList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.constraintList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.body, description);
} else if (isSdsFunction(containingDeclaration)) {
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.parameterList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.resultList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.constraintList, description);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
isSdsSegment,
isSdsStatement,
isSdsTypeArgument,
isSdsTypeParameter,
isSdsWildcardImport,
isSdsYield,
SdsAnnotation,
Expand All @@ -44,6 +45,7 @@ import {
SdsImportedDeclaration,
SdsMemberAccess,
SdsMemberType,
SdsNamedType,
SdsNamedTypeDeclaration,
type SdsParameter,
SdsPlaceholder,
Expand Down Expand Up @@ -110,7 +112,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
if (isSdsMemberType(node.$container) && node.$containerProperty === 'member') {
return this.getScopeForMemberTypeMember(node.$container);
} else {
return this.getScopeForNamedTypeDeclaration(context);
return this.getScopeForNamedTypeDeclaration(node, context);
}
} else if (isSdsReference(node) && context.property === 'target') {
if (isSdsMemberAccess(node.$container) && node.$containerProperty === 'member') {
Expand Down Expand Up @@ -192,8 +194,19 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
}
}

private getScopeForNamedTypeDeclaration(context: ReferenceInfo): Scope {
return this.coreDeclarations(SdsNamedTypeDeclaration, super.getScope(context));
private getScopeForNamedTypeDeclaration(node: SdsNamedType, context: ReferenceInfo): Scope {
// Default scope
let currentScope = this.coreDeclarations(SdsNamedTypeDeclaration, super.getScope(context));

// Type parameters (up to the containing type parameter)
const containingTypeParameter = getContainerOfType(node, isSdsTypeParameter);
if (containingTypeParameter) {
const allTypeParameters = getTypeParameters(containingTypeParameter?.$container);
const priorTypeParameters = allTypeParameters.slice(0, containingTypeParameter.$containerIndex);
currentScope = this.createScopeForNodes(priorTypeParameters, currentScope);
}

return currentScope;
}

private getScopeForMemberAccessMember(node: SdsMemberAccess): Scope {
Expand Down
Loading

0 comments on commit b81bef9

Please sign in to comment.