[Compile Time Constant Extraction] Extract Interpolated String Literals

This commit is contained in:
Venkatesh Sriram
2024-06-25 11:11:20 -07:00
parent da65626beb
commit 9820865a80
5 changed files with 352 additions and 18 deletions

View File

@@ -38,6 +38,9 @@ public:
Enum,
Type,
KeyPath,
FunctionCall,
MemberReference,
InterpolatedString,
Runtime
};
@@ -185,7 +188,7 @@ private:
std::optional<std::vector<FunctionParameter>> Parameters;
};
/// An type value representation
/// A type value representation
class TypeValue : public CompileTimeValue {
public:
TypeValue(swift::Type Type) : CompileTimeValue(ValueKind::Type), Type(Type) {}
@@ -228,6 +231,68 @@ private:
std::vector<Component> Components;
};
/// A function call representation. This is for a function declaration such as
/// let foo = bar(baz: "abc")
class FunctionCallValue : public CompileTimeValue {
public:
FunctionCallValue(std::string Identifier,
std::optional<std::vector<FunctionParameter>> Parameters)
: CompileTimeValue(ValueKind::FunctionCall), Identifier(Identifier),
Parameters(Parameters) {}
std::string getIdentifier() const { return Identifier; }
std::optional<std::vector<FunctionParameter>> getParameters() const {
return Parameters;
}
static bool classof(const CompileTimeValue *T) {
return T->getKind() == ValueKind::FunctionCall;
}
private:
std::string Identifier;
std::optional<std::vector<FunctionParameter>> Parameters;
};
/// A member reference representation such as
/// let foo = MyStruct.bar
class MemberReferenceValue : public CompileTimeValue {
public:
MemberReferenceValue(swift::Type BaseType, std::string MemberLabel)
: CompileTimeValue(ValueKind::MemberReference), BaseType(BaseType),
MemberLabel(MemberLabel) {}
std::string getMemberLabel() const { return MemberLabel; }
swift::Type getBaseType() const { return BaseType; }
static bool classof(const CompileTimeValue *T) {
return T->getKind() == ValueKind::MemberReference;
}
private:
swift::Type BaseType;
std::string MemberLabel;
};
/// A representation of an Interpolated String Literal
class InterpolatedStringLiteralValue : public CompileTimeValue {
public:
InterpolatedStringLiteralValue(
std::vector<std::shared_ptr<CompileTimeValue>> Segments)
: CompileTimeValue(ValueKind::InterpolatedString), Segments(Segments) {}
std::vector<std::shared_ptr<CompileTimeValue>> getSegments() const {
return Segments;
}
static bool classof(const CompileTimeValue *T) {
return T->getKind() == ValueKind::InterpolatedString;
}
private:
std::vector<std::shared_ptr<CompileTimeValue>> Segments;
};
/// A representation of an arbitrary value that does not fall under
/// any of the above categories.
class RuntimeValue : public CompileTimeValue {

View File

@@ -286,13 +286,25 @@ static std::shared_ptr<CompileTimeValue> extractCompileTimeValue(Expr *expr) {
case ExprKind::Call: {
auto callExpr = cast<CallExpr>(expr);
if (callExpr->getFn()->getKind() == ExprKind::ConstructorRefCall) {
auto functionKind = callExpr->getFn()->getKind();
if (functionKind == ExprKind::DeclRef) {
auto declRefExpr = cast<DeclRefExpr>(callExpr->getFn());
auto caseName =
declRefExpr->getDecl()->getName().getBaseIdentifier().str().str();
std::vector<FunctionParameter> parameters =
extractFunctionArguments(callExpr->getArgs());
return std::make_shared<FunctionCallValue>(caseName, parameters);
}
if (functionKind == ExprKind::ConstructorRefCall) {
std::vector<FunctionParameter> parameters =
extractFunctionArguments(callExpr->getArgs());
return std::make_shared<InitCallValue>(callExpr->getType(), parameters);
}
if (callExpr->getFn()->getKind() == ExprKind::DotSyntaxCall) {
if (functionKind == ExprKind::DotSyntaxCall) {
auto dotSyntaxCallExpr = cast<DotSyntaxCallExpr>(callExpr->getFn());
auto fn = dotSyntaxCallExpr->getFn();
if (fn->getKind() == ExprKind::DeclRef) {
@@ -408,6 +420,38 @@ static std::shared_ptr<CompileTimeValue> extractCompileTimeValue(Expr *expr) {
return extractCompileTimeValue(injectIntoOptionalExpr->getSubExpr());
}
case ExprKind::Load: {
auto loadExpr = cast<LoadExpr>(expr);
return extractCompileTimeValue(loadExpr->getSubExpr());
}
case ExprKind::MemberRef: {
auto memberExpr = cast<MemberRefExpr>(expr);
if (isa<TypeExpr>(memberExpr->getBase())) {
auto baseTypeExpr = cast<TypeExpr>(memberExpr->getBase());
auto label = memberExpr->getDecl().getDecl()->getBaseIdentifier().str();
return std::make_shared<MemberReferenceValue>(
baseTypeExpr->getInstanceType(), label.str());
}
break;
}
case ExprKind::InterpolatedStringLiteral: {
auto interpolatedStringExpr = cast<InterpolatedStringLiteralExpr>(expr);
auto tapExpr = interpolatedStringExpr->getAppendingExpr();
auto &Ctx = tapExpr->getVar()->getASTContext();
std::vector<std::shared_ptr<CompileTimeValue>> segments;
interpolatedStringExpr->forEachSegment(
Ctx, [&](bool isInterpolation, CallExpr *segment) -> void {
auto arg = segment->getArgs()->get(0);
auto expr = arg.getExpr();
segments.push_back(extractCompileTimeValue(expr));
});
return std::make_shared<InterpolatedStringLiteralValue>(segments);
}
default: {
break;
}
@@ -725,23 +769,69 @@ void writeValue(llvm::json::OStream &JSON,
break;
}
case CompileTimeValue::KeyPath: {
auto keyPathValue = cast<KeyPathValue>(value);
JSON.attribute("valueKind", "KeyPath");
JSON.attributeObject("value", [&]() {
JSON.attribute("path", keyPathValue->getPath());
JSON.attribute("rootType", toFullyQualifiedTypeNameString(keyPathValue->getRootType()));
JSON.attributeArray("components", [&] {
auto components = keyPathValue->getComponents();
for (auto c : components) {
case CompileTimeValue::ValueKind::KeyPath: {
auto keyPathValue = cast<KeyPathValue>(value);
JSON.attribute("valueKind", "KeyPath");
JSON.attributeObject("value", [&]() {
JSON.attribute("path", keyPathValue->getPath());
JSON.attribute("rootType", toFullyQualifiedTypeNameString(
keyPathValue->getRootType()));
JSON.attributeArray("components", [&] {
auto components = keyPathValue->getComponents();
for (auto c : components) {
JSON.object([&] {
JSON.attribute("label", c.Label);
JSON.attribute("type", toFullyQualifiedTypeNameString(c.Type));
});
}
});
});
break;
}
case CompileTimeValue::ValueKind::FunctionCall: {
auto functionCallValue = cast<FunctionCallValue>(value);
JSON.attribute("valueKind", "FunctionCall");
JSON.attributeObject("value", [&]() {
JSON.attribute("name", functionCallValue->getIdentifier());
if (functionCallValue->getParameters().has_value()) {
auto params = functionCallValue->getParameters().value();
JSON.attributeArray("arguments", [&] {
for (auto FP : params) {
JSON.object([&] {
JSON.attribute("label", c.Label);
JSON.attribute("type", toFullyQualifiedTypeNameString(c.Type));
JSON.attribute("label", FP.Label);
JSON.attribute("type", toFullyQualifiedTypeNameString(FP.Type));
writeValue(JSON, FP.Value);
});
}
});
}
});
break;
}
case CompileTimeValue::ValueKind::MemberReference: {
auto memberReferenceValue = cast<MemberReferenceValue>(value);
JSON.attribute("valueKind", "MemberReference");
JSON.attributeObject("value", [&]() {
JSON.attribute("baseType", toFullyQualifiedTypeNameString(
memberReferenceValue->getBaseType()));
JSON.attribute("memberLabel", memberReferenceValue->getMemberLabel());
});
break;
}
case CompileTimeValue::ValueKind::InterpolatedString: {
auto interpolatedStringValue = cast<InterpolatedStringLiteralValue>(value);
JSON.attribute("valueKind", "InterpolatedStringLiteral");
JSON.attributeObject("value", [&]() {
JSON.attributeArray("segments", [&] {
auto segments = interpolatedStringValue->getSegments();
for (auto s : segments) {
JSON.object([&] { writeValue(JSON, s); });
}
});
break;
});
break;
}
case CompileTimeValue::ValueKind::Runtime: {

View File

@@ -110,7 +110,24 @@ public struct Bat {
// CHECK-NEXT: {
// CHECK-NEXT: "label": "fuz",
// CHECK-NEXT: "type": "Swift.Int",
// CHECK-NEXT: "valueKind": "Runtime"
// CHECK-NEXT: "valueKind": "FunctionCall",
// CHECK-NEXT: "value": {
// CHECK-NEXT: "name": "adder",
// CHECK-NEXT: "arguments": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "",
// CHECK-NEXT: "type": "Swift.Int",
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "2"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "label": "",
// CHECK-NEXT: "type": "Swift.Int",
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "3"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
@@ -123,7 +140,24 @@ public struct Bat {
// CHECK-NEXT: "isComputed": "false",
// CHECK-NEXT: "file": "{{.*}}test{{/|\\\\}}ConstExtraction{{/|\\\\}}ExtractCalls.swift",
// CHECK-NEXT: "line": 15,
// CHECK-NEXT: "valueKind": "Runtime"
// CHECK-NEXT: "valueKind": "FunctionCall",
// CHECK-NEXT: "value": {
// CHECK-NEXT: "name": "adder",
// CHECK-NEXT: "arguments": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "",
// CHECK-NEXT: "type": "Swift.Int",
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "2"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "label": "",
// CHECK-NEXT: "type": "Swift.Int",
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "3"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "label": "init4",

View File

@@ -48,7 +48,17 @@ public struct Enums: MyProto {
// CHECK-NEXT: "mangledTypeName": "n/a - deprecated",
// CHECK-NEXT: "isStatic": "false",
// CHECK-NEXT: "isComputed": "true",
// CHECK-NEXT: "valueKind": "Runtime"
// CHECK-NEXT: "valueKind": "FunctionCall",
// CHECK-NEXT: "value": {
// CHECK-NEXT: "name": "_hashValue",
// CHECK-NEXT: "arguments": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "for",
// CHECK-NEXT: "type": "ExtractEnums.SimpleEnum",
// CHECK-NEXT: "valueKind": "Runtime"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "cases": [

View File

@@ -0,0 +1,135 @@
// RUN: %empty-directory(%t)
// RUN: echo "[MyProto]" > %t/protocols.json
// RUN: %target-swift-frontend -typecheck -emit-const-values-path %t/ExtractInterpolatedStringLiterals.swiftconstvalues -const-gather-protocols-file %t/protocols.json -primary-file %s
// RUN: cat %t/ExtractInterpolatedStringLiterals.swiftconstvalues 2>&1 | %FileCheck %s
protocol MyProto {}
func generateString(input: String) -> String {
return "function" + input
}
public struct Internal: MyProto {
static var internalTitle: String = "Inner title"
}
public struct MyType {
var nested: NestedOne
struct NestedOne {
var foo: String
}
}
public struct External: MyProto {
var interpolatedTitle = "Start Interpolation with Member Reference: \(Internal.internalTitle). Followed By Function Call: \(generateString(input: "test")). End with KeyPath: \(\MyType.nested.foo)."
}
// CHECK: [
// CHECK-NEXT: {
// CHECK-NEXT: "typeName": "ExtractInterpolatedStringLiterals.Internal",
// CHECK-NEXT: "mangledTypeName": "33ExtractInterpolatedStringLiterals8InternalV",
// CHECK-NEXT: "kind": "struct",
// CHECK-NEXT: "file": "{{.*}}test{{/|\\\\}}ConstExtraction{{/|\\\\}}ExtractInterpolatedStringLiterals.swift",
// CHECK-NEXT: "line": 13,
// CHECK-NEXT: "conformances": [
// CHECK-NEXT: "ExtractInterpolatedStringLiterals.MyProto"
// CHECK-NEXT: ],
// CHECK-NEXT: "associatedTypeAliases": [],
// CHECK-NEXT: "properties": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "internalTitle",
// CHECK-NEXT: "type": "Swift.String",
// CHECK-NEXT: "mangledTypeName": "n/a - deprecated",
// CHECK-NEXT: "isStatic": "true",
// CHECK-NEXT: "isComputed": "false",
// CHECK-NEXT: "file": "{{.*}}test{{/|\\\\}}ConstExtraction{{/|\\\\}}ExtractInterpolatedStringLiterals.swift",
// CHECK-NEXT: "line": 14,
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "Inner title"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "typeName": "ExtractInterpolatedStringLiterals.External",
// CHECK-NEXT: "mangledTypeName": "33ExtractInterpolatedStringLiterals8ExternalV",
// CHECK-NEXT: "kind": "struct",
// CHECK-NEXT: "file": "{{.*}}test{{/|\\\\}}ConstExtraction{{/|\\\\}}ExtractInterpolatedStringLiterals.swift",
// CHECK-NEXT: "line": 25,
// CHECK-NEXT: "conformances": [
// CHECK-NEXT: "ExtractInterpolatedStringLiterals.MyProto"
// CHECK-NEXT: ],
// CHECK-NEXT: "associatedTypeAliases": [],
// CHECK-NEXT: "properties": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "interpolatedTitle",
// CHECK-NEXT: "type": "Swift.String",
// CHECK-NEXT: "mangledTypeName": "n/a - deprecated",
// CHECK-NEXT: "isStatic": "false",
// CHECK-NEXT: "isComputed": "false",
// CHECK-NEXT: "file": "{{.*}}test{{/|\\\\}}ConstExtraction{{/|\\\\}}ExtractInterpolatedStringLiterals.swift",
// CHECK-NEXT: "line": 26,
// CHECK-NEXT: "valueKind": "InterpolatedStringLiteral",
// CHECK-NEXT: "value": {
// CHECK-NEXT: "segments": [
// CHECK-NEXT: {
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "Start Interpolation with Member Reference: "
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "valueKind": "MemberReference",
// CHECK-NEXT: "value": {
// CHECK-NEXT: "baseType": "ExtractInterpolatedStringLiterals.Internal",
// CHECK-NEXT: "memberLabel": "internalTitle"
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": ". Followed By Function Call: "
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "valueKind": "FunctionCall",
// CHECK-NEXT: "value": {
// CHECK-NEXT: "name": "generateString",
// CHECK-NEXT: "arguments": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "input",
// CHECK-NEXT: "type": "Swift.String",
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "test"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": ". End with KeyPath: "
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "valueKind": "KeyPath",
// CHECK-NEXT: "value": {
// CHECK-NEXT: "path": "nested.foo",
// CHECK-NEXT: "rootType": "ExtractInterpolatedStringLiterals.MyType",
// CHECK-NEXT: "components": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "nested",
// CHECK-NEXT: "type": "ExtractInterpolatedStringLiterals.MyType.NestedOne"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "label": "foo",
// CHECK-NEXT: "type": "Swift.String"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "valueKind": "RawLiteral",
// CHECK-NEXT: "value": "."
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]