diff --git a/README.md b/README.md index 9055e725b33b0..d0293dd973400 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,57 @@ -# TypeScript +# Operator Overloading + Implicit `this` TypeScript Fork - +## Installation -[![CI](https://github.com/microsoft/TypeScript/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/TypeScript/actions/workflows/ci.yml) -[![npm version](https://badge.fury.io/js/typescript.svg)](https://www.npmjs.com/package/typescript) -[![Downloads](https://img.shields.io/npm/dm/typescript.svg)](https://www.npmjs.com/package/typescript) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/microsoft/TypeScript/badge)](https://securityscorecards.dev/viewer/?uri=github.com/microsoft/TypeScript) - - -[TypeScript](https://www.typescriptlang.org/) is a language for application-scale JavaScript. TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. Try it out at the [playground](https://www.typescriptlang.org/play/), and stay up to date via [our blog](https://blogs.msdn.microsoft.com/typescript) and [Twitter account](https://twitter.com/typescript). - -Find others who are using TypeScript at [our community page](https://www.typescriptlang.org/community/). - -## Installing - -For the latest stable version: - -```bash -npm install -D typescript +```sh +npm install -D typescript@npm:@operated/typescript ``` - -For our nightly builds: - -```bash -npm install -D typescript@next +And for VS Code: +- Ctrl/⌘+Shift+P +- Open Settings +- Type in Search "tsdk" +- Put path `./node_modules/typescript/lib/` +- Restart VS Code + +## Usage + +When operators are run on objects, TypeScript checks whether there is a method to call, instead of an operator. In final JS, it would look like an object method was called. (`a + b` => `a.plus(b)`) + +Along with a compiler, "Go To Definition" feature was supported for IDE: click on an operator between the objects to jump to method, that will be invoked. + +| Operator | Method Name | +|----|----| +| +, += | plus | +| -, -= | minus | +| *, *= | times | +| /, /= | div, invDiv* | +| %, %= | rem | +| ^. ^= | pow | +| ==, != | equals | +| ===, !== | exactEquals | +| a() | run | + +_* — `invDiv` is inverted division, needed when divided type is not an object. For example, `2 / vec2(1, 0)` will result in `vec2(1, 0).invDiv(2)`_ + +## Implicit `this.` + +As another feature, this fork supports referencing properties/methods inside class, which is a usual feature in `Java`/`C#`/`C++`/etc. For example: +```typescript +class Class { + property1 = 123 + method() { + property1++ + console.log(property1) + } +} ``` -## Contribute - -**NOTE: Code changes in this repo are now limited to a small category of fixes**: - - * Crashes that were introduced in 5.9 or 6.0 that *also* repro in 7.0 *and* have a portable fix *and* don't incur other behavioral changes - * Security issues - * Language service crashes that substantially impact mainline usage - * Serious regressions from 5.9 (these must *seriously* impact a *large* proportion of users) - -Most bug fixes should be submitted to the [typescript-go](https://github.com/microsoft/TypeScript-go) repository. -Feature additions and behavorial changes are currently on pause until TypeScript 7.0 is completed. - -There are many ways to [contribute](https://github.com/microsoft/TypeScript/blob/main/CONTRIBUTING.md) to TypeScript. -* [Submit bugs](https://github.com/microsoft/TypeScript/issues) and help us verify fixes as they are checked in. -* Review the [source code changes](https://github.com/microsoft/TypeScript/pulls). -* Engage with other TypeScript users and developers on [StackOverflow](https://stackoverflow.com/questions/tagged/typescript). -* Help each other in the [TypeScript Community Discord](https://discord.gg/typescript). -* Join the [#typescript](https://twitter.com/search?q=%23TypeScript) discussion on Twitter. -* [Contribute bug fixes](https://github.com/microsoft/TypeScript/blob/main/CONTRIBUTING.md). - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see -the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) -with any additional questions or comments. - -## Documentation - -* [TypeScript in 5 minutes](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) -* [Programming handbook](https://www.typescriptlang.org/docs/handbook/intro.html) -* [Homepage](https://www.typescriptlang.org/) - -## Roadmap - -For details on our planned features and future direction, please refer to our [roadmap](https://github.com/microsoft/TypeScript/wiki/Roadmap). +Additionally, you can also add these settings to VS Code to highlight references with implicit this: +```json +"editor.semanticTokenColorCustomizations": { + "enabled": true, + "rules": { + "property.local:typescript": { "foreground": "#9cdcfe" }, + "member.local:typescript": { "foreground": "#dcdcaa" } + } +} +``` \ No newline at end of file diff --git a/package.json b/package.json index 7efedce412d6c..07da3d3bfabb5 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "typescript", + "name": "@operated/typescript", "author": "Microsoft Corp.", "homepage": "https://www.typescriptlang.org/", - "version": "6.0.0", + "version": "6.0.5", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ @@ -13,11 +13,11 @@ "javascript" ], "bugs": { - "url": "https://github.com/microsoft/TypeScript/issues" + "url": "https://github.com/dkaraush/TypeScript/issues" }, "repository": { "type": "git", - "url": "https://github.com/microsoft/TypeScript.git" + "url": "https://github.com/dkaraush/TypeScript.git" }, "main": "./lib/typescript.js", "typings": "./lib/typescript.d.ts", diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0567712f11da3..b21633b87823f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2939,6 +2939,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const nodeId = getNodeId(node); return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); } + + function setResolvedOperator(node: BinaryExpression | PrefixUnaryExpression | ElementAccessExpression, sym: Symbol, name: __String, isInverted: boolean, isUnary: boolean, isAccess: boolean, isNegated: boolean, isCompoundAssignment: boolean) { + const links = getNodeLinks(node); + links.resolvedOperatorSymbol = sym; + links.resolvedOperatorMethodName = name; + links.resolvedOperatorIsInverted = isInverted; + links.resolvedOperatorIsUnary = isUnary; + links.resolvedOperatorIsAccess = isAccess; + links.resolvedOperatorIsNegated = isNegated; + links.resolvedOperatorIsCompoundAssignment = isCompoundAssignment; + } function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { if (meaning) { @@ -3239,7 +3250,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { nameNotFoundMessage: DiagnosticMessage, ) { const name = isString(nameArg) ? nameArg : (nameArg as Identifier).escapedText; + if (errorLocation && isIdentifier(errorLocation) && (meaning & SymbolFlags.Value) && tryResolveImplicitThisMember(errorLocation)) { + return; + } addLazyDiagnostic(() => { + if (errorLocation && isIdentifier(errorLocation) && tryResolveImplicitThisMember(errorLocation)) { + return; + } if ( !errorLocation || errorLocation.parent.kind !== SyntaxKind.JSDocLink && @@ -27771,19 +27788,107 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getResolvedSymbol(node: Identifier): Symbol { const links = getNodeLinks(node); if (!links.resolvedSymbol) { - links.resolvedSymbol = !nodeIsMissing(node) && - resolveName( - node, - node, - SymbolFlags.Value | SymbolFlags.ExportValue, - getCannotFindNameDiagnosticForName(node), - !isWriteOnlyAccess(node), - /*excludeGlobals*/ false, - ) || unknownSymbol; + if (nodeIsMissing(node)) { + links.resolvedSymbol = unknownSymbol; + return links.resolvedSymbol; + } + const resolved = resolveName( + node, + node, + SymbolFlags.Value | SymbolFlags.ExportValue, + getCannotFindNameDiagnosticForName(node), + !isWriteOnlyAccess(node), + /*excludeGlobals*/ false, + ); + if (resolved) { + links.resolvedSymbol = resolved; + } + else { + const implicit = tryResolveImplicitThisMember(node); + if (implicit) { + links.resolvedSymbol = implicit; + links.isImplicitThisReference = true; + (getCheckFlags(implicit) & CheckFlags.Instantiated ? getSymbolLinks(implicit).target : implicit)!.isReferenced = SymbolFlags.All; + checkImplicitThisAccessibility(node, implicit); + } + else { + links.resolvedSymbol = unknownSymbol; + } + } } return links.resolvedSymbol; } + function tryResolveImplicitThisMember(node: Identifier): Symbol | undefined { + const name = node.escapedText; + if (!name) return undefined; + if (node.parent && (isTypeNode(node.parent) || isTypeQueryNode(node.parent))) return undefined; + const parent = node.parent; + if (!parent) return undefined; + if (isRightSideOfQualifiedNameOrPropertyAccess(node)) return undefined; + if (isDeclarationName(node)) return undefined; + if (isImportOrExportSpecifier(parent) || isImportClause(parent) || isImportEqualsDeclaration(parent)) return undefined; + + let cur: Node = node; + while (cur.parent) { + const p: Node = cur.parent; + if (p.kind === SyntaxKind.Decorator) return undefined; + if (p.kind === SyntaxKind.HeritageClause) return undefined; + if (p.kind === SyntaxKind.ArrowFunction) { + cur = p; + continue; + } + if ( + p.kind === SyntaxKind.FunctionDeclaration || + p.kind === SyntaxKind.FunctionExpression + ) { + return undefined; + } + if (isClassElement(p) && p.parent && isClassLike(p.parent)) { + const classNode = p.parent as ClassLikeDeclaration; + const isStatic = hasStaticModifier(p) || p.kind === SyntaxKind.ClassStaticBlockDeclaration; + const sym = lookupImplicitClassMember(classNode, name, isStatic); + if (sym) { + const links = getNodeLinks(node); + links.implicitThisIsStatic = isStatic; + const className = classNode.name ? idText(classNode.name) : undefined; + links.implicitThisClassName = className; + } + return sym; + } + cur = p; + if (cur.kind === SyntaxKind.SourceFile) return undefined; + } + return undefined; + } + + function checkImplicitThisAccessibility(node: Identifier, prop: Symbol): void { + const modFlags = prop.valueDeclaration ? getSelectedEffectiveModifierFlags(prop.valueDeclaration, ModifierFlags.NonPublicAccessibilityModifier) : 0; + if (!modFlags) return; + const links = getNodeLinks(node); + const className = links.implicitThisClassName; + let classNode: Node | undefined = node; + while (classNode && !(isClassLike(classNode) && (!className || (classNode.name && idText(classNode.name) === className)))) { + classNode = classNode.parent; + } + if (!classNode || !isClassLike(classNode)) return; + const classSymbol = getSymbolOfDeclaration(classNode); + if (!classSymbol) return; + const containingType = links.implicitThisIsStatic ? getTypeOfSymbol(classSymbol) : getDeclaredTypeOfClassOrInterface(classSymbol); + checkPropertyAccessibilityAtLocation(node, /*isSuper*/ false, /*writing*/ false, containingType, prop, node); + } + + function lookupImplicitClassMember(classNode: ClassLikeDeclaration, name: __String, isStatic: boolean): Symbol | undefined { + const classSymbol = getSymbolOfDeclaration(classNode); + if (!classSymbol) return undefined; + if (isStatic) { + const staticType = getTypeOfSymbol(classSymbol); + return getPropertyOfType(staticType, name); + } + const instanceType = getDeclaredTypeOfClassOrInterface(classSymbol); + return getPropertyOfType(instanceType, name); + } + function isInAmbientOrTypeNode(node: Node): boolean { return !!(node.flags & NodeFlags.Ambient || findAncestor(node, n => isInterfaceDeclaration(n) || isTypeAliasDeclaration(n) || isTypeLiteralNode(n))); } @@ -27872,6 +27977,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (targetPropertyName !== undefined) { return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); } + if ( + isIdentifier(target) && + target.escapedText === sourcePropertyName && + isPropertyAccessExpression(source) && + source.expression.kind === SyntaxKind.ThisKeyword + ) { + getResolvedSymbol(target); + if (getNodeLinks(target).isImplicitThisReference) { + return true; + } + } } if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) { const symbol = getResolvedSymbol(source.argumentExpression); @@ -31118,7 +31234,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (assignmentKind) { if ( !(localOrExportSymbol.flags & SymbolFlags.Variable) && - !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule) + !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule) && + !getNodeLinks(node).isImplicitThisReference ) { const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum : localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class @@ -35541,6 +35658,37 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const indexExpression = node.argumentExpression; const indexType = checkExpression(indexExpression); + const lhsApparent = getApparentType(getTypeOfExpression(node.expression)); + const methodName = "run" as __String + const methodSym = getPropertyOfType(lhsApparent, methodName); + if (methodSym) { + const methodType = getTypeOfSymbolAtLocation(methodSym, node.expression); + const callSigs = getSignaturesOfType(methodType, SignatureKind.Call); + if (some(callSigs)) { + for (const sig of callSigs) { + const params = sig.parameters; + if (params.length < 1) continue; + + const p0 = params[0]; + const p0Type = getTypeOfSymbolAtLocation(p0, node.argumentExpression); + if (isTypeAssignableTo(getTypeOfExpression(node.argumentExpression), p0Type)) { + const returnT = getReturnTypeOfSignature(sig); + setResolvedOperator( + node, + methodSym, + methodName, + false, + false, + true, + false, + false + ) + return returnT; + } + } + } + } + if (isErrorType(objectType) || objectType === silentNeverType) { return objectType; } @@ -36192,6 +36340,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; const effectiveCheckArgumentNode = getEffectiveCheckNode(arg); if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? effectiveCheckArgumentNode : undefined, effectiveCheckArgumentNode, headMessage, containingMessageChain, errorOutputContainer)) { + const liftedSig = tryImplicitFunctionLiftForArg(arg, paramType); + if (liftedSig) { + setImplicitLift(arg, liftedSig); // record for emit + continue; // treat this arg as compatible + } + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); maybeAddMissingAwaitInfo(arg, checkArgType, paramType); return errorOutputContainer.errors || emptyArray; @@ -36225,6 +36379,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } + + function tryImplicitFunctionLiftForArg(argExpr: Expression, paramType: Type): Signature | undefined { + const paramSigs = getSignaturesOfType(paramType, SignatureKind.Call); + if (!paramSigs.length) return; + const argT = getTypeOfExpression(argExpr); + if (getSignaturesOfType(argT, SignatureKind.Call).length) return; + + for (const sig of paramSigs) { + const ret = getReturnTypeOfSignature(sig); + if (isTypeAssignableTo(argT, ret)) + return sig; + } + } + + function setImplicitLift(node: Expression, sig: Signature) { + const links = getNodeLinks(node); + links.implicitLiftSignature = sig; + } } /** @@ -39987,6 +40159,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (operandType === silentNeverType) { return silentNeverType; } + const methodName = getUnaryOperatorMethodName(node.operator); + if (methodName) { + const apparent = getApparentType(operandType); + const methodSym = getPropertyOfType(apparent, methodName); + if (methodSym) { + const mType = getTypeOfSymbolAtLocation(methodSym, node.operand); + const sigs = getSignaturesOfType(mType, SignatureKind.Call); + const match = firstDefined(sigs, sig => sig.parameters.length === 0 ? sig : undefined); + if (match) { + setResolvedOperator( + node, + methodSym, + methodName, + false, + true, + false, + false, + false + ) + return getReturnTypeOfSignature(match); + } + } + } switch (node.operand.kind) { case SyntaxKind.NumericLiteral: switch (node.operator) { @@ -40711,6 +40906,58 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, checkMode, errorNode); } + function tryResolveBinaryOperatorOverload( + operatorToken: BinaryOperatorToken, + methodName: __String | undefined, + left: Expression, + right: Expression, + leftType: Type, + rightType: Type, + inverted: boolean, + isNegated: boolean, + isCompoundAssignment: boolean + ): Type | undefined { + if (!methodName) return undefined; + + const lhsApparent = getApparentType(leftType); + const methodSym = getPropertyOfType(lhsApparent, methodName); + if (!methodSym) return undefined; + + const methodType = getTypeOfSymbolAtLocation(methodSym, left); + const callSigs = getSignaturesOfType(methodType, SignatureKind.Call); + if (!some(callSigs)) { + return undefined; + } + + for (const sig of callSigs) { + const params = sig.parameters; + if (params.length < 1) continue; + + const p0 = params[0]; + const p0Type = getTypeOfSymbolAtLocation(p0, right); + if (isTypeAssignableTo(rightType, p0Type)) { + const returnT = getReturnTypeOfSignature(sig); + + const parent = operatorToken.parent; + if (isBinaryExpression(parent)) { + setResolvedOperator( + parent, + methodSym, + methodName, + inverted, + false, + false, + isNegated, + isCompoundAssignment + ) + return returnT; + } + } + } + + return undefined; + } + function checkBinaryLikeExpressionWorker( left: Expression, operatorToken: BinaryOperatorToken, @@ -40721,6 +40968,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { errorNode?: Node, ): Type { const operator = operatorToken.kind; + const operatorIsNegated = operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken; + const operatorIsCompoundAssignment = isCompoundAssignment(operator); switch (operator) { case SyntaxKind.AsteriskToken: case SyntaxKind.AsteriskAsteriskToken: @@ -40750,6 +40999,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { leftType = checkNonNullType(leftType, left); rightType = checkNonNullType(rightType, right); + + let resultTypeOverloaded = tryResolveBinaryOperatorOverload( + operatorToken, + getBinaryOperatorMethodName(operator, false), + left, right, + leftType, rightType, + false, + operatorIsNegated, + operatorIsCompoundAssignment + ) + if (!resultTypeOverloaded) { + resultTypeOverloaded = tryResolveBinaryOperatorOverload( + operatorToken, + getBinaryOperatorMethodName(operator, true), + right, left, + rightType, leftType, + true, + operatorIsNegated, + operatorIsCompoundAssignment + ) + } + if (resultTypeOverloaded) { + return resultTypeOverloaded; + } let suggestedOperator: PunctuationSyntaxKind | undefined; // if a user tries to apply a bitwise operator to 2 boolean operands @@ -40851,7 +41124,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Otherwise, the result is of type Any. // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. resultType = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; - } + } else { + resultType = tryResolveBinaryOperatorOverload( + operatorToken, + getBinaryOperatorMethodName(operator, false), + left, right, + leftType, rightType, + false, + operatorIsNegated, + operatorIsCompoundAssignment + ) + if (!resultType) { + resultType = tryResolveBinaryOperatorOverload( + operatorToken, + getBinaryOperatorMethodName(operator, true), + right, left, + rightType, leftType, + true, + operatorIsNegated, + operatorIsCompoundAssignment + ) + } + if (resultType) { + return resultType; + } + } // Symbols are not allowed at all in arithmetic expressions if (resultType && !checkForDisallowedESSymbolOperand(operator)) { @@ -40896,7 +41193,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: { + let eqOverload = tryResolveBinaryOperatorOverload( + operatorToken, + getBinaryOperatorMethodName(operator, false), + left, right, + leftType, rightType, + false, + operatorIsNegated, + operatorIsCompoundAssignment + ); + if (!eqOverload) { + eqOverload = tryResolveBinaryOperatorOverload( + operatorToken, + getBinaryOperatorMethodName(operator, true), + right, left, + rightType, leftType, + true, + operatorIsNegated, + operatorIsCompoundAssignment + ); + } + if (eqOverload) { + return eqOverload; + } // We suppress errors in CheckMode.TypeOnly (meaning the invocation came from getTypeOfExpression). During // control flow analysis it is possible for operands to temporarily have narrower types, and those narrower // types may cause the operands to not be comparable. We don't want such errors reported (see #46475). @@ -40913,6 +41233,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); } return booleanType; + } case SyntaxKind.InstanceOfKeyword: return checkInstanceOfExpression(left, right, leftType, rightType, checkMode); case SyntaxKind.InKeyword: @@ -50077,7 +50398,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); return symbol === unknownSymbol ? undefined : symbol; } - const result = resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + let result = resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!result && !isJSDoc) { + const implicit = tryResolveImplicitThisMember(name); + if (implicit) result = implicit; + } if (!result && isJSDoc) { const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); if (container) { @@ -51517,6 +51842,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { symbolToDeclarations: (symbol, meaning, flags, maximumLength, verbosityLevel, out) => { return nodeBuilder.symbolToDeclarations(symbol, meaning, flags, maximumLength, verbosityLevel, out); }, + getResolvedOperatorInfo(node: Node) { + const links = getNodeLinks(node as any); + const name = (links as any).resolvedOperatorMethodName as __String | undefined; + const isUnary = !!(links as any).resolvedOperatorIsUnary; + const isInverted = !!(links as any).resolvedOperatorIsInverted; + const isAccess = !!(links as any).resolvedOperatorIsAccess; + const isNegated = !!(links as any).resolvedOperatorIsNegated; + const isCompoundAssignment = !!(links as any).resolvedOperatorIsCompoundAssignment; + return name ? { name, isUnary, isInverted, isAccess, isNegated, isCompoundAssignment } : undefined; + }, + getImplicitLift(node: Node) { + return getNodeLinks(node as any)?.implicitLiftSignature; + }, + getImplicitThisInfo(node: Node) { + const links = getNodeLinks(node as any); + if (!links || !(links as any).isImplicitThisReference) return undefined; + return { + isStatic: !!(links as any).implicitThisIsStatic, + className: (links as any).implicitThisClassName as string | undefined, + }; + }, }; function isImportRequiredByAugmentation(node: ImportDeclaration) { @@ -54247,6 +54593,51 @@ function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) return "nextType"; } } + +export function getBinaryOperatorMethodName(op: SyntaxKind, inverted: boolean): __String | undefined { + switch (op) { + case SyntaxKind.PlusToken: + case SyntaxKind.PlusEqualsToken: + return "plus" as __String; + case SyntaxKind.MinusToken: + case SyntaxKind.MinusEqualsToken: + return "minus" as __String; + case SyntaxKind.AsteriskToken: + case SyntaxKind.AsteriskEqualsToken: + return "times" as __String; + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + if (inverted) + return "invDiv" as __String; + else + return "div" as __String; + case SyntaxKind.PercentToken: + case SyntaxKind.PercentEqualsToken: + return "rem" as __String; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + return "equals" as __String; + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + return "exactEquals" as __String; + case SyntaxKind.CaretToken: + case SyntaxKind.CaretEqualsToken: + return "pow" as __String; + default: return undefined + } +} + +export function getUnaryOperatorMethodName(op: SyntaxKind): __String | undefined { + switch (op) { + case SyntaxKind.PlusToken: + return "unaryPlus" as __String; + case SyntaxKind.MinusToken: + return "unaryMinus" as __String; + case SyntaxKind.ExclamationToken: + return "not" as __String; + default: return undefined + } +} /** @internal */ export function signatureHasRestParameter(s: Signature): boolean { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5a4858b572dc1..6670360665656 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1186,6 +1186,9 @@ export const notImplementedResolver: EmitResolver = { isDefinitelyReferenceToGlobalSymbolObject: notImplemented, createLateBoundIndexSignatures: notImplemented, symbolToDeclarations: notImplemented, + getResolvedOperatorInfo: notImplemented, + getImplicitLift: notImplemented, + getImplicitThisInfo: notImplemented, }; const enum PipelinePhase { diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 6d9884632482e..9c859eaf9a3d6 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -74,6 +74,9 @@ import { VariableDeclaration, } from "./_namespaces/ts.js"; import * as performance from "./_namespaces/ts.performance.js"; +import { transformOperatorOverloads } from "./transformers/operatorOverloads"; +import { transformImplicitLifts } from "./transformers/implicitLifts"; +import { transformImplicitThis } from "./transformers/implicitThis"; function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { switch (moduleKind) { @@ -136,6 +139,10 @@ function getScriptTransformers(compilerOptions: CompilerOptions, customTransform transformers.push(transformTypeScript); + transformers.push(transformOperatorOverloads); + transformers.push(transformImplicitLifts); + transformers.push(transformImplicitThis); + if (compilerOptions.experimentalDecorators) { transformers.push(transformLegacyDecorators); } diff --git a/src/compiler/transformers/implicitLifts.ts b/src/compiler/transformers/implicitLifts.ts new file mode 100644 index 0000000000000..ea1fcccfdf561 --- /dev/null +++ b/src/compiler/transformers/implicitLifts.ts @@ -0,0 +1,42 @@ +import { isCallExpression, isSourceFile } from "../factory/nodeTests"; +import { Bundle, Expression, Node, SourceFile, SyntaxKind, TransformationContext } from "../types"; +import { visitEachChild, visitNode } from "../visitorPublic"; + +export function transformImplicitLifts(context: TransformationContext) { + const f = context.factory; + const resolver = context.getEmitResolver(); + + const visit = (node: Node): Node => { + if (isCallExpression(node)) { + const newArgs = node.arguments.map((arg) => { + const sig = resolver.getImplicitLift(arg); + if (!sig) + return visitEachChild(arg, visit, context); + + return f.createArrowFunction( + undefined, undefined, [], undefined, + f.createToken(SyntaxKind.EqualsGreaterThanToken), + visitEachChild(arg, visit, context) as Expression + ); + }); + + return f.updateCallExpression( + node, + visitEachChild(node.expression, visit, context) as Expression, + node.typeArguments, + newArgs + ); + } + + return visitEachChild(node, visit, context); + }; + + const transformSourceFile = (sf: SourceFile): SourceFile => + visitNode(sf, visit) as SourceFile; + + const transformBundle = (b: Bundle): Bundle => + f.createBundle(b.sourceFiles.map(transformSourceFile)); + + return (node: SourceFile | Bundle): SourceFile | Bundle => + isSourceFile(node) ? transformSourceFile(node) : transformBundle(node); +} \ No newline at end of file diff --git a/src/compiler/transformers/implicitThis.ts b/src/compiler/transformers/implicitThis.ts new file mode 100644 index 0000000000000..a744cce5e02fd --- /dev/null +++ b/src/compiler/transformers/implicitThis.ts @@ -0,0 +1,30 @@ +import { isIdentifier, isSourceFile } from "../factory/nodeTests"; +import { Bundle, Identifier, Node, SourceFile, TransformationContext } from "../types"; +import { visitEachChild, visitNode } from "../visitorPublic"; + +export function transformImplicitThis(context: TransformationContext) { + const f = context.factory; + const resolver = context.getEmitResolver(); + + const visit = (node: Node): Node => { + if (isIdentifier(node)) { + const info = resolver.getImplicitThisInfo?.(node); + if (info) { + const receiver = info.isStatic && info.className + ? f.createIdentifier(info.className) + : f.createThis(); + return f.createPropertyAccessExpression(receiver, node as Identifier); + } + } + return visitEachChild(node, visit, context); + }; + + const transformSourceFile = (sf: SourceFile): SourceFile => + visitNode(sf, visit) as SourceFile; + + const transformBundle = (b: Bundle): Bundle => + f.createBundle(b.sourceFiles.map(transformSourceFile)); + + return (node: SourceFile | Bundle): SourceFile | Bundle => + isSourceFile(node) ? transformSourceFile(node) : transformBundle(node); +} diff --git a/src/compiler/transformers/operatorOverloads.ts b/src/compiler/transformers/operatorOverloads.ts new file mode 100644 index 0000000000000..e2d1383ed7ed0 --- /dev/null +++ b/src/compiler/transformers/operatorOverloads.ts @@ -0,0 +1,95 @@ +import { isBinaryExpression, isElementAccessExpression, isPrefixUnaryExpression, isSourceFile } from "../factory/nodeTests"; +import { Bundle, Expression, Node, SourceFile, SyntaxKind, TransformationContext } from "../types"; +import { visitEachChild, visitNode } from "../visitorPublic"; + +export function transformOperatorOverloads(context: TransformationContext) { + const f = context.factory; + const resolver = context.getEmitResolver(); + + const visit = (node: Node): Node => { + if (isBinaryExpression(node)) { + const info = resolver.getResolvedOperatorInfo?.(node); + if (info && !info.isUnary) { + const left = visitNode(node.left, visit) as Expression; + const right = visitNode(node.right, visit) as Expression; + switch (node.operatorToken.kind) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.AsteriskToken: + case SyntaxKind.SlashToken: + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.CaretToken: + case SyntaxKind.PercentToken: { + const receiver = info.isInverted ? right : left; + const arg = info.isInverted ? left : right; + const call = f.createCallExpression( + f.createPropertyAccessExpression(receiver, info.name as string), + undefined, + [arg] + ); + // != and !== → negate the result: !a.equals(b) + if (info.isNegated) { + return f.createPrefixUnaryExpression(SyntaxKind.ExclamationToken, call); + } + return call; + } + case SyntaxKind.PlusEqualsToken: + case SyntaxKind.MinusEqualsToken: + case SyntaxKind.AsteriskEqualsToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.PercentEqualsToken: + case SyntaxKind.CaretEqualsToken: { + // a += b → a = a.plus(b) + const receiver = info.isInverted ? right : left; + const arg = info.isInverted ? left : right; + const call = f.createCallExpression( + f.createPropertyAccessExpression(receiver, info.name as string), + undefined, + [arg] + ); + return f.createAssignment(left, call); + } + } + } + } + + if (isPrefixUnaryExpression(node)) { + const info = resolver.getResolvedOperatorInfo?.(node); + if (info && info.isUnary) { + const operand = visitNode(node.operand, visit) as Expression; + return f.createCallExpression( + f.createPropertyAccessExpression(operand, info.name as string), + undefined, + [] + ); + } + } + + if (isElementAccessExpression(node)) { + const info = resolver.getResolvedOperatorInfo?.(node); + if (info && info.isAccess) { + const expression = visitNode(node.expression, visit) as Expression; + const argument = visitNode(node.argumentExpression, visit) as Expression; + return f.createCallExpression( + f.createPropertyAccessExpression(expression, info.name as string), + undefined, + [argument] + ); + } + } + + return visitEachChild(node, visit, context); + }; + + const transformSourceFile = (sf: SourceFile): SourceFile => + visitNode(sf, visit) as SourceFile; + + const transformBundle = (b: Bundle): Bundle => + f.createBundle(b.sourceFiles.map(transformSourceFile)); + + return (node: SourceFile | Bundle): SourceFile | Bundle => + isSourceFile(node) ? transformSourceFile(node) : transformBundle(node); +} diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4d8d22afb54e6..502f1a908fb3c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5936,6 +5936,9 @@ export interface EmitResolver { isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean; createLateBoundIndexSignatures(cls: ClassLikeDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, internalFlags: InternalNodeBuilderFlags, tracker: SymbolTracker): (IndexSignatureDeclaration | PropertyDeclaration)[] | undefined; symbolToDeclarations(symbol: Symbol, meaning: SymbolFlags, flags: NodeBuilderFlags, maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut): Declaration[]; + getResolvedOperatorInfo(node: Node): { name: string, isUnary: boolean, isInverted: boolean, isAccess: boolean, isNegated: boolean, isCompoundAssignment: boolean } | undefined + getImplicitLift(node: Node): Signature | undefined + getImplicitThisInfo(node: Node): { isStatic: boolean, className: string | undefined } | undefined } // dprint-ignore @@ -6308,6 +6311,17 @@ export interface NodeLinks { externalHelpersModule?: Symbol; // Resolved symbol for the external helpers module instantiationExpressionTypes?: Map; // Cache of instantiation expression types for the node nonExistentPropCheckCache?: Set; + resolvedOperatorSymbol?: Symbol; + resolvedOperatorMethodName?: __String; + resolvedOperatorIsInverted?: boolean; + resolvedOperatorIsUnary?: boolean; + resolvedOperatorIsAccess?: boolean; + resolvedOperatorIsNegated?: boolean; + resolvedOperatorIsCompoundAssignment?: boolean; + implicitLiftSignature?: Signature; + isImplicitThisReference?: boolean; + implicitThisIsStatic?: boolean; + implicitThisClassName?: string; } /** @internal */ diff --git a/src/services/classifier2020.ts b/src/services/classifier2020.ts index 529b0adc03563..be24a448af82a 100644 --- a/src/services/classifier2020.ts +++ b/src/services/classifier2020.ts @@ -188,6 +188,14 @@ function collectTokens(program: Program, sourceFile: SourceFile, span: TextSpan, if ((typeIdx === TokenType.variable || typeIdx === TokenType.function) && isLocalDeclaration(decl, sourceFile)) { modifierSet |= 1 << TokenModifier.local; } + if ( + (typeIdx === TokenType.property || typeIdx === TokenType.member) && + isIdentifier(node) && + !isRightSideOfQualifiedNameOrPropertyAccess(node) && + !(node.parent && (node.parent as NamedDeclaration).name === node) + ) { + modifierSet |= 1 << TokenModifier.local; + } if (program.isSourceFileDefaultLibrary(decl.getSourceFile())) { modifierSet |= 1 << TokenModifier.defaultLibrary; } diff --git a/src/services/completions.ts b/src/services/completions.ts index 28d29136dab89..51dd5c3787aa6 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -4045,12 +4045,10 @@ function getCompletionData( } } - // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` - if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) { + if (scopeNode.kind !== SyntaxKind.SourceFile) { const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false, isClassLike(scopeNode.parent) ? scopeNode as ThisContainer : undefined); if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) { for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { - symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; symbols.push(symbol); symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; } diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 0a45cc3e3e9a0..390feee447e62 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -22,6 +22,7 @@ import { FindAllReferences, findAncestor, first, + firstDefined, flatMap, forEach, FunctionLikeDeclaration, @@ -33,6 +34,7 @@ import { getNameFromPropertyName, getNameOfDeclaration, getObjectFlags, + getBinaryOperatorMethodName, getPropertySymbolsFromContextualType, getTargetLabel, getTextOfPropertyName, @@ -44,6 +46,7 @@ import { isAnyImportOrBareOrAccessedRequire, isAssignmentDeclaration, isAssignmentExpression, + isBinaryExpression, isBindingElement, isCallLikeExpression, isCallOrNewExpressionTarget, @@ -96,6 +99,7 @@ import { resolvePath, ScriptElementKind, SignatureDeclaration, + SignatureKind, skipAlias, skipParentheses, skipTrivia, @@ -115,6 +119,9 @@ import { TypeFlags, TypeReference, unescapeLeadingUnderscores, + getUnaryOperatorMethodName, + isUnaryExpression, + isPrefixUnaryExpression, } from "./_namespaces/ts.js"; /** @internal */ @@ -141,6 +148,60 @@ export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile } } + if (getBinaryOperatorMethodName(node.kind, false)) { + const bin = findAncestor(node, isBinaryExpression); + if (bin && node === bin.operatorToken) { + const opName = getBinaryOperatorMethodName(node.kind, false)! as string; + + const lhsT = typeChecker.getTypeAtLocation(bin.left); + const rhsT = typeChecker.getTypeAtLocation(bin.right); + + const lhsApp = typeChecker.getApparentType(lhsT); + const methodSym = typeChecker.getPropertyOfType(lhsApp, opName); + if (methodSym) { + const methodType = typeChecker.getTypeOfSymbolAtLocation(methodSym, bin.left); + const sigs = typeChecker.getSignaturesOfType(methodType, SignatureKind.Call); + + const match = firstDefined(sigs, sig => { + const p0 = sig.parameters[0]; + if (!p0) return undefined; + const p0Type = typeChecker.getTypeOfSymbolAtLocation(p0, bin.right); + return typeChecker.isTypeAssignableTo(rhsT, p0Type) ? sig : undefined; + }); + + if (match) { + const decls = methodSym.declarations || emptyArray; + if (decls.length) { + return flatMap(decls, d => + getDefinitionFromSymbol(typeChecker, d.symbol, node) + ); + } + } + } + } + } + if (getUnaryOperatorMethodName(node.kind)) { + const bin = findAncestor(node, isPrefixUnaryExpression); + if (bin) { + const opName = getUnaryOperatorMethodName(node.kind)! as string; + const operandType = typeChecker.getTypeAtLocation(bin.operand); + const operandApp = typeChecker.getApparentType(operandType); + const methodSym = typeChecker.getPropertyOfType(operandApp, opName); + if (methodSym) { + const methodType = typeChecker.getTypeOfSymbolAtLocation(methodSym, bin.operand); + const sigs = typeChecker.getSignaturesOfType(methodType, SignatureKind.Call); + if (sigs.length) { + const decls = methodSym.declarations || emptyArray; + if (decls.length) { + return flatMap(decls, d => + getDefinitionFromSymbol(typeChecker, d.symbol, node) + ); + } + } + } + } + } + // Labels if (isJumpStatementTarget(node)) { const label = getTargetLabel(node.parent, node.text); diff --git a/src/services/services.ts b/src/services/services.ts index 9885093ed6180..96d7ce778def1 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -347,6 +347,11 @@ import { updateSourceFile, UserPreferences, VariableDeclaration, + getBinaryOperatorMethodName, + isBinaryExpression, + getUnaryOperatorMethodName, + isPrefixUnaryExpression, + isNamedDeclaration } from "./_namespaces/ts.js"; import * as NavigateTo from "./_namespaces/ts.NavigateTo.js"; import * as NavigationBar from "./_namespaces/ts.NavigationBar.js"; @@ -2278,13 +2283,71 @@ export function createLanguageService( synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const node = getTouchingPropertyName(sourceFile, position); + let node = getTouchingPropertyName(sourceFile, position); if (node === sourceFile) { // Avoid giving quickInfo for the sourceFile as a whole. return undefined; } const typeChecker = program.getTypeChecker(); + if (getBinaryOperatorMethodName(node.kind, false)) { + const bin = findAncestor(node, isBinaryExpression); + if (bin && node === bin.operatorToken) { + const opName = getBinaryOperatorMethodName(node.kind, false)! as string; + + const lhsT = typeChecker.getTypeAtLocation(bin.left); + const rhsT = typeChecker.getTypeAtLocation(bin.right); + + const lhsApp = typeChecker.getApparentType(lhsT); + const methodSym = typeChecker.getPropertyOfType(lhsApp, opName); + if (methodSym) { + const methodType = typeChecker.getTypeOfSymbolAtLocation(methodSym, bin.left); + const sigs = typeChecker.getSignaturesOfType(methodType, SignatureKind.Call); + + const match = firstDefined(sigs, sig => { + const p0 = sig.parameters[0]; + if (!p0) return undefined; + const p0Type = typeChecker.getTypeOfSymbolAtLocation(p0, bin.right); + return typeChecker.isTypeAssignableTo(rhsT, p0Type) ? sig : undefined; + }); + + if (match) { + const decls = methodSym.declarations || emptyArray; + if (decls.length) { + const decl = decls[0] + const nameNode = (isNamedDeclaration(decl) && decl.name) ? decl.name : decl + if (nameNode) { + node = nameNode + } + } + } + } + } + } + if (getUnaryOperatorMethodName(node.kind)) { + const bin = findAncestor(node, isPrefixUnaryExpression); + if (bin) { + const opName = getUnaryOperatorMethodName(node.kind)! as string; + const operandType = typeChecker.getTypeAtLocation(bin.operand); + const operandApp = typeChecker.getApparentType(operandType); + const methodSym = typeChecker.getPropertyOfType(operandApp, opName); + if (methodSym) { + const methodType = typeChecker.getTypeOfSymbolAtLocation(methodSym, bin.operand); + const sigs = typeChecker.getSignaturesOfType(methodType, SignatureKind.Call); + if (sigs.length) { + const decls = methodSym.declarations || emptyArray; + if (decls.length) { + const decl = decls[0] + const nameNode = (isNamedDeclaration(decl) && decl.name) ? decl.name : decl + if (nameNode) { + node = nameNode + } + } + } + } + } + } + const nodeForQuickInfo = getNodeForQuickInfo(node); const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker); if (!symbol || typeChecker.isUnknownSymbol(symbol)) {