Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions Coder.Test/Languages/PythonGeneratedSourceImportsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,58 @@ assert isinstance(shapes.Point.zero(), shapes.Point)
});
}

/// <summary>
/// Tests that a class with a property that can be written but not read can be loaded and set.
/// </summary>
/// <remarks>
/// <c>@level.setter</c> names the property a getter made, so a property with no getter needs
/// another spelling or the class statement raises <c>NameError</c>.
/// </remarks>
[TestMethod]
public void WriteOnlyProperty_ImportsAndSets()
{
string? python = ToolchainHarness.FindOnPath("--version", "python3", "python");
if (python is null)
{
Assert.Inconclusive("No Python interpreter on the path, so nothing was run.");
return;
}

ClassDeclaration sink = new("Sink");
PropertyDeclaration level = new("level", "int") { HasGetter = false, HasSetter = true };
CallExpression record = new("record");
record.Arguments.Add(new VariableReference("value"));
level.SetterBody.Add(new ExpressionStatement(record));
sink.Members.Add(level);

ToolchainHarness.InTemporaryDirectory(directory =>
{
File.WriteAllText(Path.Combine(directory, "sink.py"), new PythonGenerator().Generate(sink));
File.WriteAllText(
Path.Combine(directory, "driver.py"),
"""
import runpy

recorded = []
module = runpy.run_path("sink.py", init_globals={"record": recorded.append})

instance = module["Sink"]()
instance.level = 3
assert recorded == [3], recorded

try:
instance.level
except AttributeError:
pass
else:
raise AssertionError("a write-only property could be read")
""");

(int exitCode, string output) = ToolchainHarness.Run(python, "driver.py", directory);
Assert.AreEqual(0, exitCode, $"Python rejected the generated module:{Environment.NewLine}{output}");
});
}

/// <summary>
/// Builds a file whose declarations are each written over themselves.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions Coder/Languages/PythonGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@
WriteAccessorBody(declaration.GetterBody, code);
}

if (declaration.CanWrite && !declaration.CanRead)
{
// `@name.setter` is an attribute of the property the getter made, so with no getter there
// is nothing for the decorator to name and the class statement raises NameError. The
// property is built from the setter alone instead.
string setter = $"_set_{name}";
code.Write($"def {setter}(self, value{annotation}) -> None:");
WriteAccessorBody(declaration.SetterBody, code);
code.WriteLine($"{name} = property(fset={setter})");
return;
}

if (declaration.CanWrite)
{
code.WriteLine($"@{name}.setter");
Expand Down Expand Up @@ -207,7 +219,7 @@
/// <c>float("nan")</c> does not.
/// </remarks>
protected override string SpellNonFiniteDouble(double value) =>
double.IsNaN(value) ? "float(\"nan\")" : value > 0 ? "float(\"inf\")" : "float(\"-inf\")";

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 222 in Coder/Languages/PythonGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

/// <inheritdoc/>
protected override IEnumerable<string> RequiredImports(SourceFile file)
Expand Down
Loading