Skip to content

Commit 1ac9809

Browse files
authored
Merge branch 'master' into tests/add-attr-and-descriptor-tests
2 parents ceaac1f + dc5d294 commit 1ac9809

File tree

5 files changed

+237
-5
lines changed

5 files changed

+237
-5
lines changed

src/compile.js

+61-3
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,13 @@ Compiler.prototype.buildcodeobj = function (n, coname, decorator_list, args, cal
20662066
out(scopename, ".co_varnames=[];");
20672067
}
20682068

2069+
//
2070+
// Skulpt doesn't have "co_consts", so record the docstring (or
2071+
// None) in the "co_docstring" property of the code object, ready
2072+
// for use by the Sk.builtin.func constructor.
2073+
//
2074+
out(scopename, ".co_docstring=", this.cDocstringOfCode(n), ";");
2075+
20692076
//
20702077
// attach flags
20712078
//
@@ -2115,7 +2122,6 @@ Compiler.prototype.buildcodeobj = function (n, coname, decorator_list, args, cal
21152122
"\",arguments.length,0,0);return new Sk.builtins['generator'](", scopename, ",$gbl,[]", frees, ");}))");
21162123
}
21172124
} else {
2118-
var res;
21192125
if (decos.length > 0) {
21202126
out("$ret = new Sk.builtins['function'](", scopename, ",$gbl", frees, ");");
21212127
for (let decorator of decos.reverse()) {
@@ -2129,6 +2135,47 @@ Compiler.prototype.buildcodeobj = function (n, coname, decorator_list, args, cal
21292135
}
21302136
};
21312137

2138+
/** JavaScript for the docstring of the given body, or null if the
2139+
* body has no docstring.
2140+
*/
2141+
Compiler.prototype.maybeCDocstringOfBody = function(body) {
2142+
if (body.length === 0) // Don't think this can happen?
2143+
return null;
2144+
2145+
const stmt_0 = body[0];
2146+
if (stmt_0.constructor !== Sk.astnodes.Expr)
2147+
return null;
2148+
2149+
const expr = stmt_0.value;
2150+
if (expr.constructor !== Sk.astnodes.Str)
2151+
return null;
2152+
2153+
return this.vexpr(expr);
2154+
};
2155+
2156+
/** JavaScript for the docstring of the given node. Only called from
2157+
* buildcodeobj(), and expects a FunctionDef, Lambda, or GeneratorExp
2158+
* node. We give a "None" docstring to a GeneratorExp node, although
2159+
* it is not carried over to the final generator; this is harmless.
2160+
*/
2161+
Compiler.prototype.cDocstringOfCode = function(node) {
2162+
switch (node.constructor) {
2163+
case Sk.astnodes.AsyncFunctionDef: // For when it's supported
2164+
case Sk.astnodes.FunctionDef:
2165+
return (
2166+
this.maybeCDocstringOfBody(node.body)
2167+
|| "Sk.builtin.none.none$"
2168+
);
2169+
2170+
case Sk.astnodes.Lambda:
2171+
case Sk.astnodes.GeneratorExp:
2172+
return "Sk.builtin.none.none$";
2173+
2174+
default:
2175+
Sk.asserts.fail(`unexpected node kind ${node.constructor.name}`);
2176+
}
2177+
}
2178+
21322179
Compiler.prototype.cfunction = function (s, class_for_super) {
21332180
var funcorgen;
21342181
Sk.asserts.assert(s instanceof Sk.astnodes.FunctionDef);
@@ -2669,8 +2716,19 @@ Compiler.prototype.exitScope = function () {
26692716
* @param {Sk.builtin.str=} class_for_super
26702717
*/
26712718
Compiler.prototype.cbody = function (stmts, class_for_super) {
2672-
var i;
2673-
for (i = 0; i < stmts.length; ++i) {
2719+
var i = 0;
2720+
2721+
// If we have a docstring, then assign it to __doc__, and skip over
2722+
// the expression when properly compiling the rest of the body. This
2723+
// happens for class and module bodies.
2724+
//
2725+
const maybeDocstring = this.maybeCDocstringOfBody(stmts);
2726+
if (maybeDocstring !== null) {
2727+
out("$loc.__doc__ = ", maybeDocstring, ";");
2728+
i = 1;
2729+
}
2730+
2731+
for (; i < stmts.length; ++i) {
26742732
this.vstmt(stmts[i], class_for_super);
26752733
}
26762734
};

src/function.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Sk.builtin.func = Sk.abstr.buildNativeClass("function", {
3434

3535
this.$name = (code.co_name && code.co_name.v) || code.name || "<native JS>";
3636
this.$d = Sk.builtin.dict ? new Sk.builtin.dict() : undefined;
37-
this.$doc = code.$doc;
37+
this.$doc = code.co_docstring || Sk.builtin.none.none$;
3838
this.$module = (Sk.globals && Sk.globals["__name__"]) || Sk.builtin.none.none$;
3939
this.$qualname = (code.co_qualname && code.co_qualname.v) || this.$name;
4040

@@ -127,7 +127,13 @@ Sk.builtin.func = Sk.abstr.buildNativeClass("function", {
127127
},
128128
__doc__: {
129129
$get() {
130-
return new Sk.builtin.str(this.$doc);
130+
return this.$doc;
131+
},
132+
$set(v) {
133+
// The value the user is setting __doc__ to can be any Python
134+
// object. If we receive 'undefined' then the user is deleting
135+
// __doc__, which is allowed and results in __doc__ being None.
136+
this.$doc = v || Sk.builtin.none.none$;
131137
},
132138
},
133139
},

test/unit3/copydocstring.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Copy docstring"""
2+
3+
doc = __doc__

test/unit3/overwritedocstring.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""First docstring"""
2+
3+
__doc__ = "Second docstring"

test/unit3/test_docstring.py

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import unittest
2+
3+
4+
def banana():
5+
"Yellow"
6+
return 42
7+
8+
9+
def orange():
10+
"Oran" "ge"
11+
12+
13+
def blackbirds():
14+
4 + 20 # Not a docstring
15+
16+
17+
def do_nothing():
18+
pass
19+
20+
21+
def make_adder(n):
22+
"Function adding N"
23+
def add(x):
24+
"Compute N + X"
25+
return n + x
26+
return add
27+
28+
29+
class Strawberry:
30+
"Delicious"
31+
doc = __doc__
32+
33+
def weight(self):
34+
"Heavy"
35+
return 0.25
36+
37+
@classmethod
38+
def is_red(cls):
39+
"Very red"
40+
return True
41+
42+
@staticmethod
43+
def pick():
44+
"Picked"
45+
return None
46+
47+
48+
class Tangerine:
49+
def peel():
50+
pass
51+
52+
53+
class Pear:
54+
"This will not be the __doc__"
55+
__doc__ = "Conference"
56+
57+
58+
class TestDocstrings(unittest.TestCase):
59+
def test_builtin(self):
60+
self.assertTrue(divmod.__doc__.startswith("Return the tuple (x//y, x%y)"))
61+
62+
def test_library_function(self):
63+
# This test will need updating if/when random.seed gets a docstring.
64+
# It also fails under cpython.
65+
import random
66+
self.assertEqual(random.seed.__doc__, None)
67+
68+
def test_function(self):
69+
self.assertEqual(banana.__doc__, "Yellow")
70+
self.assertEqual(orange.__doc__, "Orange")
71+
self.assertEqual(make_adder.__doc__, "Function adding N")
72+
73+
def test_runtime_function(self):
74+
self.assertEqual(make_adder(3).__doc__, "Compute N + X")
75+
76+
def test_non_string_expr(self):
77+
self.assertEqual(blackbirds.__doc__, None)
78+
79+
def test_no_expr(self):
80+
self.assertEqual(do_nothing.__doc__, None)
81+
82+
def test_class(self):
83+
self.assertEqual(Strawberry.__doc__, "Delicious")
84+
self.assertEqual(Strawberry.doc, "Delicious")
85+
86+
def test_class_no_docstring(self):
87+
self.assertEqual(Tangerine.__doc__, None)
88+
self.assertEqual(Tangerine.peel.__doc__, None)
89+
self.assertEqual(Tangerine().peel.__doc__, None)
90+
91+
def test_class_explicit_docstring(self):
92+
self.assertEqual(Pear.__doc__, "Conference")
93+
94+
def test_method(self):
95+
self.assertEqual(Strawberry.weight.__doc__, "Heavy")
96+
s = Strawberry()
97+
self.assertEqual(s.weight.__doc__, "Heavy")
98+
99+
def test_classmethod(self):
100+
self.assertEqual(Strawberry.is_red.__doc__, "Very red")
101+
102+
def test_staticmethod(self):
103+
self.assertEqual(Strawberry.pick.__doc__, "Picked")
104+
105+
def test_module(self):
106+
import bisect
107+
self.assertEqual(bisect.__doc__, "Bisection algorithms.")
108+
109+
def test_local_module(self):
110+
import copydocstring
111+
self.assertEqual(copydocstring.__doc__, "Copy docstring")
112+
self.assertEqual(copydocstring.doc, "Copy docstring")
113+
114+
def test_local_module_overwriting_docstring(self):
115+
import overwritedocstring
116+
self.assertEqual(overwritedocstring.__doc__, "Second docstring")
117+
118+
def test_lambda(self):
119+
f = lambda x: 42
120+
self.assertEqual(f.__doc__, None)
121+
122+
def test_setting_on_function(self):
123+
def f():
124+
"hello"
125+
return 42
126+
127+
self.assertEqual(f.__doc__, "hello")
128+
129+
f.__doc__ = "world"
130+
self.assertEqual(f.__doc__, "world")
131+
132+
# Setting to a non-string is odd but allowed:
133+
f.__doc__ = 42
134+
self.assertEqual(f.__doc__, 42)
135+
136+
# Attemping to delete __doc__ instead sets it to None:
137+
del f.__doc__
138+
self.assertEqual(f.__doc__, None)
139+
140+
def test_setting_on_method(self):
141+
class Banana:
142+
def peel(self):
143+
"Remove peel"
144+
pass
145+
146+
self.assertEqual(Banana.peel.__doc__, "Remove peel")
147+
148+
# We can set the doc when accessed on the class:
149+
Banana.peel.__doc__ = "Take out of peel"
150+
self.assertEqual(Banana.peel.__doc__, "Take out of peel")
151+
152+
# But not when accessed via an instance:
153+
154+
def set_on_method_of_instance():
155+
banana = Banana()
156+
banana.peel.__doc__ = "this will not work"
157+
158+
self.assertRaises(AttributeError, set_on_method_of_instance)
159+
160+
161+
if __name__ == '__main__':
162+
unittest.main()

0 commit comments

Comments
 (0)